@ng-native/components 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,10 +13,14 @@ export type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';
13
13
  *
14
14
  * The overlap is the view's own frame against the keyboard's top edge, so a view that does not
15
15
  * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is
16
- * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in
17
- * the parent's coordinates, and on a screen under a navigation bar those start a bar's height
16
+ * measured in the window, and the keyboard's `screenY` brought into it: `(layout)` reports the top
17
+ * in the parent's coordinates, and on a screen under a navigation bar those start a bar's height
18
18
  * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by
19
- * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the
19
+ * exactly that much, its bottom edge still behind the keyboard. On Android, when React Native's
20
+ * edge-to-edge flag is off (Expo Go leaves it off), the window starts below the status bar while
21
+ * `screenY` counts from the top of the screen, so the keyboard's top is taken `Keyboard.windowTop`
22
+ * higher in the window, or the view comes up short by the status bar; when the flag is on,
23
+ * `Keyboard.windowTop` is zero. The overlap never exceeds the
20
24
  * view's own height either: padding a view by more than it has pushes its content out of it.
21
25
  * When the keyboard source reports no `screenY`, the keyboard's height stands in.
22
26
  *
@@ -31,10 +31,14 @@ function layoutEasingOf(easing) {
31
31
  *
32
32
  * The overlap is the view's own frame against the keyboard's top edge, so a view that does not
33
33
  * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is
34
- * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in
35
- * the parent's coordinates, and on a screen under a navigation bar those start a bar's height
34
+ * measured in the window, and the keyboard's `screenY` brought into it: `(layout)` reports the top
35
+ * in the parent's coordinates, and on a screen under a navigation bar those start a bar's height
36
36
  * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by
37
- * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the
37
+ * exactly that much, its bottom edge still behind the keyboard. On Android, when React Native's
38
+ * edge-to-edge flag is off (Expo Go leaves it off), the window starts below the status bar while
39
+ * `screenY` counts from the top of the screen, so the keyboard's top is taken `Keyboard.windowTop`
40
+ * higher in the window, or the view comes up short by the status bar; when the flag is on,
41
+ * `Keyboard.windowTop` is zero. The overlap never exceeds the
38
42
  * view's own height either: padding a view by more than it has pushes its content out of it.
39
43
  * When the keyboard source reports no `screenY`, the keyboard's height stands in.
40
44
  *
@@ -91,7 +95,8 @@ export class KeyboardAvoidingView extends ViewBase {
91
95
  // had before, as RN adds its current adjustment back; otherwise the room it made reads as no
92
96
  // overlap and the height springs back under the keyboard.
93
97
  const extent = this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;
94
- const overlap = screenY === undefined ? height - offset : frame.y + extent - (screenY - offset);
98
+ const top = screenY === undefined ? undefined : screenY - this.keyboardService.windowTop;
99
+ const overlap = top === undefined ? height - offset : frame.y + extent - (top - offset);
95
100
  return Math.min(extent, Math.max(0, overlap));
96
101
  }, /* @ts-ignore */
97
102
  ...(ngDevMode ? [{ debugName: "overlap" }] : /* istanbul ignore next */ []));
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-avoiding-view.js","sourceRoot":"","sources":["../src/keyboard-avoiding-view.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n effect,\n inject,\n input,\n signal,\n booleanAttribute,\n} from '@angular/core';\nimport { Keyboard, LayoutAnimation, type LayoutEasing } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport type { Rect } from './events.ts';\nimport { ContentContainer } from './content-container.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\nimport { numberOr } from './transforms.ts';\n\nexport type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';\n\n/**\n * The names `LayoutEasing` knows. `'keyboard'` is among them: iOS reports its keyboard's own\n * curve by that name, and RN's KeyboardAvoidingView animates on it.\n */\nconst LAYOUT_EASINGS: ReadonlySet<string> = new Set([\n 'linear',\n 'easeInEaseOut',\n 'easeIn',\n 'easeOut',\n 'spring',\n 'keyboard',\n]);\n\n/** A keyboard event's `easing`, narrowed to what `LayoutAnimation` accepts, or left to its default. */\nfunction layoutEasingOf(easing: string | undefined): LayoutEasing | undefined {\n return easing !== undefined && LAYOUT_EASINGS.has(easing) ? (easing as LayoutEasing) : undefined;\n}\n\n/**\n * Moves its content clear of the keyboard.\n *\n * RN's KeyboardAvoidingView is a JS composite: there is no native component, only a view whose\n * padding, height or position tracks the keyboard. Off a device the `Keyboard` service reports\n * nothing, so this renders as a plain view and does nothing, which is also the right behaviour on\n * a platform with no soft keyboard.\n *\n * The overlap is the view's own frame against the keyboard's top edge, so a view that does not\n * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is\n * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in\n * the parent's coordinates, and on a screen under a navigation bar those start a bar's height\n * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by\n * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the\n * view's own height either: padding a view by more than it has pushes its content out of it.\n * When the keyboard source reports no `screenY`, the keyboard's height stands in.\n *\n * The adjustment itself animates with the keyboard's own motion, the way RN's own\n * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next\n * layout animation before the style change that moves this view, so the two run together rather\n * than the padding, height or position jumping to its resting place in one step. Android animates\n * no layout unless an app turns that on, and reports no duration, so there the view jumps; with\n * `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.\n */\n@Component({\n selector: 'keyboard-avoiding-view',\n imports: [ContentContainer, View],\n template: `\n @if (behavior() === 'position') {\n <view\n [contentContainerOf]=\"node\"\n [contentContainerClass]=\"contentContainerClass()\"\n [style]=\"positioned()\"\n ><ng-content\n /></view>\n } @else {\n <ng-content />\n }\n `,\n host: {\n '[styleOverride]': 'avoidance()',\n '(layout)': 'onLayout($event)',\n },\n})\nexport class KeyboardAvoidingView extends ViewBase {\n /**\n * How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this\n * unset and does nothing; `padding` is the default here because that is what a form wants.\n */\n readonly behavior = input<KeyboardAvoidingBehavior>('padding');\n /** Styles for the inner view that `position` moves. */\n readonly contentContainerStyle = input<Record<string, unknown>>();\n /**\n * Classes for the inner view that `position` moves, matched as if it were written in the\n * template this view is. See `ContentContainer`.\n */\n readonly contentContainerClass = input<string>();\n /** Set to false to stop avoiding without removing the view. */\n readonly enabled = input(true, { transform: booleanAttribute });\n /**\n * Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a\n * header's height; here the view is measured in the window, so a navigation bar needs none.\n */\n readonly keyboardVerticalOffset = input(0, { transform: numberOr(0) });\n\n private readonly keyboardService = inject(Keyboard);\n /** Where the keyboard is, at every frame of its motion where the platform says. */\n private readonly keyboard = this.keyboardService.position;\n private readonly layoutAnimation = inject(LayoutAnimation);\n private readonly frame = signal<Rect | null>(null);\n /** The height before any adjustment, which `height` mode shrinks from. */\n private initialHeight: number | null = null;\n\n /** How far the keyboard intrudes into this view, in points. */\n protected readonly overlap = computed(() => {\n if (!this.enabled()) return 0;\n const { height, screenY } = this.keyboard();\n if (height === 0) return 0;\n const frame = this.frame();\n const offset = this.keyboardVerticalOffset();\n if (!frame) return Math.max(0, height - offset);\n // In `height` mode the frame is the one this view already shrank, so measure the height it\n // had before, as RN adds its current adjustment back; otherwise the room it made reads as no\n // overlap and the height springs back under the keyboard.\n const extent =\n this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;\n const overlap = screenY === undefined ? height - offset : frame.y + extent - (screenY - offset);\n return Math.min(extent, Math.max(0, overlap));\n });\n\n /**\n * The adjustment, or nothing to adjust. Bound as the engine's style override rather than as\n * host style, because Angular ranks a host style binding under the template's: a caller's\n * `[style]=\"{flex: 1}\"` would otherwise keep its flex in height mode and the view would not\n * shrink at all. RN composes the adjustment after the caller's style, so it wins there too.\n */\n protected readonly avoidance = computed(() => {\n const overlap = this.overlap();\n const behavior = this.behavior();\n if (overlap === 0 || behavior === 'position') return undefined;\n if (behavior === 'height') {\n const base = this.initialHeight ?? this.frame()?.height;\n return base === undefined ? undefined : { height: base - overlap, flex: 0 };\n }\n return { paddingBottom: overlap };\n });\n\n protected readonly positioned = computed(() => ({\n ...this.contentContainerStyle(),\n bottom: this.overlap(),\n }));\n\n constructor() {\n super();\n // A duration of zero is Android's own keyboard event, which says nothing of the motion; there\n // is nothing to configure alongside it. Nor where the view follows the motion frame by frame.\n // `appear`/`leave` are turned off because this is an update to a view already on screen, not\n // one appearing or leaving - RN's own bare `LayoutAnimation.configureNext({duration, update})`\n // animates nothing else either.\n effect(() => {\n const { duration, easing } = this.keyboardService.metrics();\n if (!this.enabled() || !duration || this.keyboardService.followsMotion) return;\n void this.layoutAnimation.animate(() => {}, {\n duration,\n easing: layoutEasingOf(easing),\n appear: 'none',\n leave: 'none',\n });\n });\n }\n\n protected onLayout(event: NativeSyntheticEvent<{ layout?: Rect }>): void {\n const layout = event.nativeEvent?.layout;\n if (!layout) return;\n this.frame.set({ ...layout, y: this.windowTop(layout.y) });\n // Only a layout that the keyboard did not cause counts as the natural height.\n if (this.overlap() === 0) this.initialHeight = layout.height;\n }\n\n /**\n * Where this view's top edge is in the window, or `fallback` where the platform cannot say.\n * Synchronous on the new architecture, so it answers for the layout being reported.\n */\n private windowTop(fallback: number): number {\n let top = fallback;\n this.engine.measure(this.node, (frame) => (top = frame.y));\n return top;\n }\n}\n"]}
1
+ {"version":3,"file":"keyboard-avoiding-view.js","sourceRoot":"","sources":["../src/keyboard-avoiding-view.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n effect,\n inject,\n input,\n signal,\n booleanAttribute,\n} from '@angular/core';\nimport { Keyboard, LayoutAnimation, type LayoutEasing } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport type { Rect } from './events.ts';\nimport { ContentContainer } from './content-container.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\nimport { numberOr } from './transforms.ts';\n\nexport type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';\n\n/**\n * The names `LayoutEasing` knows. `'keyboard'` is among them: iOS reports its keyboard's own\n * curve by that name, and RN's KeyboardAvoidingView animates on it.\n */\nconst LAYOUT_EASINGS: ReadonlySet<string> = new Set([\n 'linear',\n 'easeInEaseOut',\n 'easeIn',\n 'easeOut',\n 'spring',\n 'keyboard',\n]);\n\n/** A keyboard event's `easing`, narrowed to what `LayoutAnimation` accepts, or left to its default. */\nfunction layoutEasingOf(easing: string | undefined): LayoutEasing | undefined {\n return easing !== undefined && LAYOUT_EASINGS.has(easing) ? (easing as LayoutEasing) : undefined;\n}\n\n/**\n * Moves its content clear of the keyboard.\n *\n * RN's KeyboardAvoidingView is a JS composite: there is no native component, only a view whose\n * padding, height or position tracks the keyboard. Off a device the `Keyboard` service reports\n * nothing, so this renders as a plain view and does nothing, which is also the right behaviour on\n * a platform with no soft keyboard.\n *\n * The overlap is the view's own frame against the keyboard's top edge, so a view that does not\n * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is\n * measured in the window, and the keyboard's `screenY` brought into it: `(layout)` reports the top\n * in the parent's coordinates, and on a screen under a navigation bar those start a bar's height\n * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by\n * exactly that much, its bottom edge still behind the keyboard. On Android, when React Native's\n * edge-to-edge flag is off (Expo Go leaves it off), the window starts below the status bar while\n * `screenY` counts from the top of the screen, so the keyboard's top is taken `Keyboard.windowTop`\n * higher in the window, or the view comes up short by the status bar; when the flag is on,\n * `Keyboard.windowTop` is zero. The overlap never exceeds the\n * view's own height either: padding a view by more than it has pushes its content out of it.\n * When the keyboard source reports no `screenY`, the keyboard's height stands in.\n *\n * The adjustment itself animates with the keyboard's own motion, the way RN's own\n * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next\n * layout animation before the style change that moves this view, so the two run together rather\n * than the padding, height or position jumping to its resting place in one step. Android animates\n * no layout unless an app turns that on, and reports no duration, so there the view jumps; with\n * `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.\n */\n@Component({\n selector: 'keyboard-avoiding-view',\n imports: [ContentContainer, View],\n template: `\n @if (behavior() === 'position') {\n <view\n [contentContainerOf]=\"node\"\n [contentContainerClass]=\"contentContainerClass()\"\n [style]=\"positioned()\"\n ><ng-content\n /></view>\n } @else {\n <ng-content />\n }\n `,\n host: {\n '[styleOverride]': 'avoidance()',\n '(layout)': 'onLayout($event)',\n },\n})\nexport class KeyboardAvoidingView extends ViewBase {\n /**\n * How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this\n * unset and does nothing; `padding` is the default here because that is what a form wants.\n */\n readonly behavior = input<KeyboardAvoidingBehavior>('padding');\n /** Styles for the inner view that `position` moves. */\n readonly contentContainerStyle = input<Record<string, unknown>>();\n /**\n * Classes for the inner view that `position` moves, matched as if it were written in the\n * template this view is. See `ContentContainer`.\n */\n readonly contentContainerClass = input<string>();\n /** Set to false to stop avoiding without removing the view. */\n readonly enabled = input(true, { transform: booleanAttribute });\n /**\n * Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a\n * header's height; here the view is measured in the window, so a navigation bar needs none.\n */\n readonly keyboardVerticalOffset = input(0, { transform: numberOr(0) });\n\n private readonly keyboardService = inject(Keyboard);\n /** Where the keyboard is, at every frame of its motion where the platform says. */\n private readonly keyboard = this.keyboardService.position;\n private readonly layoutAnimation = inject(LayoutAnimation);\n private readonly frame = signal<Rect | null>(null);\n /** The height before any adjustment, which `height` mode shrinks from. */\n private initialHeight: number | null = null;\n\n /** How far the keyboard intrudes into this view, in points. */\n protected readonly overlap = computed(() => {\n if (!this.enabled()) return 0;\n const { height, screenY } = this.keyboard();\n if (height === 0) return 0;\n const frame = this.frame();\n const offset = this.keyboardVerticalOffset();\n if (!frame) return Math.max(0, height - offset);\n // In `height` mode the frame is the one this view already shrank, so measure the height it\n // had before, as RN adds its current adjustment back; otherwise the room it made reads as no\n // overlap and the height springs back under the keyboard.\n const extent =\n this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;\n const top = screenY === undefined ? undefined : screenY - this.keyboardService.windowTop;\n const overlap = top === undefined ? height - offset : frame.y + extent - (top - offset);\n return Math.min(extent, Math.max(0, overlap));\n });\n\n /**\n * The adjustment, or nothing to adjust. Bound as the engine's style override rather than as\n * host style, because Angular ranks a host style binding under the template's: a caller's\n * `[style]=\"{flex: 1}\"` would otherwise keep its flex in height mode and the view would not\n * shrink at all. RN composes the adjustment after the caller's style, so it wins there too.\n */\n protected readonly avoidance = computed(() => {\n const overlap = this.overlap();\n const behavior = this.behavior();\n if (overlap === 0 || behavior === 'position') return undefined;\n if (behavior === 'height') {\n const base = this.initialHeight ?? this.frame()?.height;\n return base === undefined ? undefined : { height: base - overlap, flex: 0 };\n }\n return { paddingBottom: overlap };\n });\n\n protected readonly positioned = computed(() => ({\n ...this.contentContainerStyle(),\n bottom: this.overlap(),\n }));\n\n constructor() {\n super();\n // A duration of zero is Android's own keyboard event, which says nothing of the motion; there\n // is nothing to configure alongside it. Nor where the view follows the motion frame by frame.\n // `appear`/`leave` are turned off because this is an update to a view already on screen, not\n // one appearing or leaving - RN's own bare `LayoutAnimation.configureNext({duration, update})`\n // animates nothing else either.\n effect(() => {\n const { duration, easing } = this.keyboardService.metrics();\n if (!this.enabled() || !duration || this.keyboardService.followsMotion) return;\n void this.layoutAnimation.animate(() => {}, {\n duration,\n easing: layoutEasingOf(easing),\n appear: 'none',\n leave: 'none',\n });\n });\n }\n\n protected onLayout(event: NativeSyntheticEvent<{ layout?: Rect }>): void {\n const layout = event.nativeEvent?.layout;\n if (!layout) return;\n this.frame.set({ ...layout, y: this.windowTop(layout.y) });\n // Only a layout that the keyboard did not cause counts as the natural height.\n if (this.overlap() === 0) this.initialHeight = layout.height;\n }\n\n /**\n * Where this view's top edge is in the window, or `fallback` where the platform cannot say.\n * Synchronous on the new architecture, so it answers for the layout being reported.\n */\n private windowTop(fallback: number): number {\n let top = fallback;\n this.engine.measure(this.node, (frame) => (top = frame.y));\n return top;\n }\n}\n"]}
@@ -161,7 +161,8 @@ export class KeyboardDock extends ViewBase {
161
161
  const edge = this.bottomEdge();
162
162
  if (edge === null || screenY === undefined)
163
163
  return height;
164
- return Math.max(0, edge - screenY);
164
+ // The edge is in the window, and on Android that can start below the status bar.
165
+ return Math.max(0, edge - (screenY - this.keyboard.windowTop));
165
166
  }, /* @ts-ignore */
166
167
  ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
167
168
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n EnvironmentInjector,\n InjectionToken,\n afterRenderEffect,\n computed,\n contentChild,\n effect,\n inject,\n input,\n signal,\n untracked,\n type Provider,\n} from '@angular/core';\nimport { KEYBOARD_MOTION, Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';\nimport {\n claimHost,\n nativePlatform,\n registerViewName,\n type HostNode,\n type ScrollDrive,\n type ScrollRange,\n} from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { TextInput } from './text-input.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`\n * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.\n */\nexport const KEYBOARD_CONTROLLER = new InjectionToken<boolean>('KEYBOARD_CONTROLLER', {\n providedIn: 'root',\n factory: () => false,\n});\n\n/**\n * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.\n * Nothing is imported from it: the dock drives its native views directly.\n */\nexport function provideKeyboardController(): Provider {\n return { provide: KEYBOARD_CONTROLLER, useValue: true };\n}\n\n/** What carries the keyboard's height, frame by frame, in react-native-keyboard-controller. */\nconst KEYBOARD_FEED = {\n events: ['onKeyboardMove', 'onKeyboardMoveInteractive', 'onKeyboardMoveEnd'],\n path: ['height'],\n};\n\n/** Far enough that no keyboard is taller; native interpolation takes numbers, not infinity. */\nconst FAR = 10_000;\n\n/** A native view the dock renders for itself, out of the way of the layout. */\nconst OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };\n\n/** Over the dock, out of the way of the layout, so its bottom edge is the dock's. */\nconst OVER_DOCK = { position: 'absolute', top: 0, bottom: 0, left: 0, width: 0 };\n\ntype KeyboardMoveEvent = { nativeEvent?: { height?: number } };\n\n/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */\n@Directive({\n selector: 'keyboard-controller-view',\n host: { '[enabled]': 'true' },\n})\nexport class KeyboardControllerView {\n constructor() {\n registerViewName('keyboard-controller-view', 'KeyboardControllerView');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at\n * every frame of its motion. See `KeyboardDock`.\n */\n@Directive({ selector: 'keyboard-motion-view' })\nexport class KeyboardMotionView {\n constructor() {\n const motion = inject(KEYBOARD_MOTION);\n if (motion) registerViewName('keyboard-motion-view', motion.viewName);\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with\n * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.\n */\n@Directive({\n selector: 'keyboard-gesture-area',\n host: { '[offset]': 'offset()', '[textInputNativeID]': 'textInputNativeID()' },\n})\nexport class KeyboardGestureArea {\n readonly offset = input<number>();\n readonly textInputNativeID = input<string>();\n\n constructor() {\n registerViewName('keyboard-gesture-area', 'KeyboardGestureArea');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.\n * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard\n * is down; on top of it while it is up.\n *\n * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved\n * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input\n * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll\n * view with `keyboardDismissMode=\"interactive\"`, where iOS reports no keyboard frames to\n * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of\n * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves\n * content above with it.\n *\n * On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by\n * a view of that package's that reports how far the keyboard covers the dock at every frame. It is\n * measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet\n * react-native-screens raises with the keyboard, rises only as far as it still must. Android does\n * not use react-native-keyboard-controller: its view adds the navigation bar as margins around the\n * whole app's content, which an app drawn edge to edge does not want.\n *\n * Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the\n * keyboard starts to move; on Android it jumps into place then, rather than moving with the\n * keyboard. A window the system resized for the keyboard already puts it clear, and then it is not\n * padded at all.\n *\n * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own\n * place, for content above that keeps clear of it with padding or an inset rather than moving.\n */\n@Component({\n selector: 'keyboard-dock',\n exportAs: 'keyboardDock',\n imports: [KeyboardControllerView, KeyboardGestureArea, KeyboardMotionView, TemplateSlot, View],\n // One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would\n // be given the content at compile time, whichever branch renders.\n template: `\n <ng-template #content><ng-content /></ng-template>\n @if (lifts) {\n @if (motion) {\n <keyboard-motion-view\n [style]=\"overDock\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n } @else {\n <keyboard-controller-view\n [style]=\"outOfFlow\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n <keyboard-gesture-area\n [style]=\"outOfFlow\"\n [offset]=\"barHeight()\"\n [textInputNativeID]=\"inputID()\"\n />\n }\n <view\n collapsable=\"false\"\n [style.backgroundColor]=\"backgroundColor()\"\n (layout)=\"onBarLayout($event)\"\n ><ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\"\n /></view>\n } @else {\n <ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\" />\n }\n `,\n host: {\n '[style.paddingBottom]': 'padding()',\n '[style.backgroundColor]': 'backgroundColor()',\n '(layout)': 'onLayout()',\n },\n})\nexport class KeyboardDock extends ViewBase {\n /** The bar's background, under the inset it sits above as well as the bar itself. */\n readonly backgroundColor = input<string>();\n\n private readonly keyboard = inject(Keyboard);\n private readonly safeArea = inject(SafeArea);\n protected readonly none = {};\n protected readonly outOfFlow = OUT_OF_FLOW;\n protected readonly overDock = OVER_DOCK;\n\n /** On Android, the view that reports the keyboard's motion, where the build has one. */\n protected readonly motion = nativePlatform() === 'android' ? inject(KEYBOARD_MOTION) : null;\n\n /**\n * Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:\n * neither changes under an app.\n */\n protected readonly lifts =\n this.motion !== null || (nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER));\n\n private readonly field = contentChild(TextInput, { descendants: true, read: ElementRef });\n private readonly fieldInput = contentChild(TextInput, { descendants: true });\n\n /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */\n private readonly bottomEdge = signal<number | null>(null);\n /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */\n protected readonly barHeight = signal(0);\n /**\n * The keyboard's height over the dock as native last reported it: the controller's keyboard\n * height, excluding the drag area, or the motion view's cover of the dock.\n */\n private readonly controlled = signal(0);\n\n /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */\n private readonly inset = computed(() => this.safeArea.insets().bottom);\n\n readonly covered = computed(() =>\n this.lifts ? Math.max(0, this.controlled() - this.inset()) : 0,\n );\n\n protected readonly padding = computed(() => {\n if (this.lifts) return this.inset();\n const { height, screenY } = this.keyboard.metrics();\n if (height === 0) return this.inset();\n const edge = this.bottomEdge();\n if (edge === null || screenY === undefined) return height;\n return Math.max(0, edge - screenY);\n });\n\n /**\n * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field\n * without one is given one.\n */\n protected readonly inputID = computed(() => {\n const input = this.fieldInput();\n const node = this.field()?.nativeElement as HostNode | undefined;\n if (!input || !node) return undefined;\n const own = input.nativeID() ?? input.id();\n if (own) return own;\n const id = `keyboard-dock-${++docks}`;\n untracked(() => this.engine.setProp(node, 'nativeID', id));\n return id;\n });\n\n /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */\n readonly liftRange = computed<ScrollRange>(() => {\n const inset = this.inset();\n return { input: [inset, inset + FAR], output: [0, -FAR] };\n });\n\n private source: HostNode | null = null;\n\n private readonly inFront = inject(SCREEN_IN_FRONT);\n\n constructor() {\n super();\n if (this.lifts) this.liftBar();\n // A root effect, because a covered screen's views are detached from change detection, and a\n // view's own effects wait for it.\n const front = effect(\n () => {\n const inFront = this.inFront();\n untracked(() => {\n if (!inFront && this.keyboard.height() > 0) this.keyboard.dismiss();\n });\n },\n { injector: inject(EnvironmentInjector), manualCleanup: true },\n );\n inject(DestroyRef).onDestroy(() => front.destroy());\n }\n\n /**\n * Move a view with the keyboard on the native side, as the bar is moved: content above the bar\n * that should rise with it. Null when native does not move the bar. See `keyboardLift`.\n */\n lift(view: HostNode, shift: number | null = null): ScrollDrive | null {\n if (!this.lifts) return null;\n const source = this.controllerView();\n const range = this.liftRange();\n return this.engine.driveByEvent(view, source, KEYBOARD_FEED, 'translateY', range, [], shift);\n }\n\n private liftBar(): void {\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const range = this.liftRange();\n untracked(() => {\n if (drive) return drive.update(range);\n const bar = this.barView();\n if (bar) drive = this.lift(bar);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n\n /** The controller's or the motion view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.childList.find((child) => child.nodeKind === 'element') ?? this.node;\n return this.source;\n }\n\n /** The view holding the content, the last of the dock's own. */\n private barView(): HostNode | undefined {\n return this.node.childList.filter((child) => child.nodeKind === 'element').at(-1);\n }\n\n protected onKeyboardMove(event: unknown): void {\n const height = (event as KeyboardMoveEvent).nativeEvent?.height;\n if (height !== undefined) this.controlled.set(height);\n }\n\n protected onBarLayout(event: { nativeEvent?: { layout?: { height?: number } } }): void {\n const height = event.nativeEvent?.layout?.height;\n if (height !== undefined) this.barHeight.set(height);\n }\n\n protected onLayout(): void {\n if (this.lifts) return;\n this.engine.measure(this.node, (frame) => this.bottomEdge.set(frame.y + frame.height));\n }\n}\n\nlet docks = 0;\n\n/**\n * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]=\"dock\">` around a chat's\n * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is\n * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than\n * drawn over what is above. Does nothing where native does not move the bar.\n *\n * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved\n * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view\n * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,\n * and a transform does not change what it thinks.\n */\n@Directive({\n selector: '[keyboardLift]',\n host: { '[style.paddingBottom]': 'padding()' },\n})\nexport class KeyboardLift {\n readonly dock = input.required<KeyboardDock>({ alias: 'keyboardLift' });\n\n protected readonly padding = computed(() => {\n const covered = this.dock().covered();\n return covered > 0 ? covered : undefined;\n });\n\n constructor() {\n const view = inject(ElementRef).nativeElement as HostNode;\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const dock = this.dock();\n const range = dock.liftRange();\n const covered = dock.covered();\n untracked(() => {\n if (!drive) drive = dock.lift(view, covered);\n drive?.update(range);\n drive?.shift(covered);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n}\n"]}
1
+ {"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n EnvironmentInjector,\n InjectionToken,\n afterRenderEffect,\n computed,\n contentChild,\n effect,\n inject,\n input,\n signal,\n untracked,\n type Provider,\n} from '@angular/core';\nimport { KEYBOARD_MOTION, Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';\nimport {\n claimHost,\n nativePlatform,\n registerViewName,\n type HostNode,\n type ScrollDrive,\n type ScrollRange,\n} from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { TextInput } from './text-input.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`\n * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.\n */\nexport const KEYBOARD_CONTROLLER = new InjectionToken<boolean>('KEYBOARD_CONTROLLER', {\n providedIn: 'root',\n factory: () => false,\n});\n\n/**\n * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.\n * Nothing is imported from it: the dock drives its native views directly.\n */\nexport function provideKeyboardController(): Provider {\n return { provide: KEYBOARD_CONTROLLER, useValue: true };\n}\n\n/** What carries the keyboard's height, frame by frame, in react-native-keyboard-controller. */\nconst KEYBOARD_FEED = {\n events: ['onKeyboardMove', 'onKeyboardMoveInteractive', 'onKeyboardMoveEnd'],\n path: ['height'],\n};\n\n/** Far enough that no keyboard is taller; native interpolation takes numbers, not infinity. */\nconst FAR = 10_000;\n\n/** A native view the dock renders for itself, out of the way of the layout. */\nconst OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };\n\n/** Over the dock, out of the way of the layout, so its bottom edge is the dock's. */\nconst OVER_DOCK = { position: 'absolute', top: 0, bottom: 0, left: 0, width: 0 };\n\ntype KeyboardMoveEvent = { nativeEvent?: { height?: number } };\n\n/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */\n@Directive({\n selector: 'keyboard-controller-view',\n host: { '[enabled]': 'true' },\n})\nexport class KeyboardControllerView {\n constructor() {\n registerViewName('keyboard-controller-view', 'KeyboardControllerView');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at\n * every frame of its motion. See `KeyboardDock`.\n */\n@Directive({ selector: 'keyboard-motion-view' })\nexport class KeyboardMotionView {\n constructor() {\n const motion = inject(KEYBOARD_MOTION);\n if (motion) registerViewName('keyboard-motion-view', motion.viewName);\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with\n * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.\n */\n@Directive({\n selector: 'keyboard-gesture-area',\n host: { '[offset]': 'offset()', '[textInputNativeID]': 'textInputNativeID()' },\n})\nexport class KeyboardGestureArea {\n readonly offset = input<number>();\n readonly textInputNativeID = input<string>();\n\n constructor() {\n registerViewName('keyboard-gesture-area', 'KeyboardGestureArea');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.\n * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard\n * is down; on top of it while it is up.\n *\n * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved\n * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input\n * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll\n * view with `keyboardDismissMode=\"interactive\"`, where iOS reports no keyboard frames to\n * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of\n * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves\n * content above with it.\n *\n * On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by\n * a view of that package's that reports how far the keyboard covers the dock at every frame. It is\n * measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet\n * react-native-screens raises with the keyboard, rises only as far as it still must. Android does\n * not use react-native-keyboard-controller: its view adds the navigation bar as margins around the\n * whole app's content, which an app drawn edge to edge does not want.\n *\n * Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the\n * keyboard starts to move; on Android it jumps into place then, rather than moving with the\n * keyboard. A window the system resized for the keyboard already puts it clear, and then it is not\n * padded at all.\n *\n * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own\n * place, for content above that keeps clear of it with padding or an inset rather than moving.\n */\n@Component({\n selector: 'keyboard-dock',\n exportAs: 'keyboardDock',\n imports: [KeyboardControllerView, KeyboardGestureArea, KeyboardMotionView, TemplateSlot, View],\n // One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would\n // be given the content at compile time, whichever branch renders.\n template: `\n <ng-template #content><ng-content /></ng-template>\n @if (lifts) {\n @if (motion) {\n <keyboard-motion-view\n [style]=\"overDock\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n } @else {\n <keyboard-controller-view\n [style]=\"outOfFlow\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n <keyboard-gesture-area\n [style]=\"outOfFlow\"\n [offset]=\"barHeight()\"\n [textInputNativeID]=\"inputID()\"\n />\n }\n <view\n collapsable=\"false\"\n [style.backgroundColor]=\"backgroundColor()\"\n (layout)=\"onBarLayout($event)\"\n ><ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\"\n /></view>\n } @else {\n <ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\" />\n }\n `,\n host: {\n '[style.paddingBottom]': 'padding()',\n '[style.backgroundColor]': 'backgroundColor()',\n '(layout)': 'onLayout()',\n },\n})\nexport class KeyboardDock extends ViewBase {\n /** The bar's background, under the inset it sits above as well as the bar itself. */\n readonly backgroundColor = input<string>();\n\n private readonly keyboard = inject(Keyboard);\n private readonly safeArea = inject(SafeArea);\n protected readonly none = {};\n protected readonly outOfFlow = OUT_OF_FLOW;\n protected readonly overDock = OVER_DOCK;\n\n /** On Android, the view that reports the keyboard's motion, where the build has one. */\n protected readonly motion = nativePlatform() === 'android' ? inject(KEYBOARD_MOTION) : null;\n\n /**\n * Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:\n * neither changes under an app.\n */\n protected readonly lifts =\n this.motion !== null || (nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER));\n\n private readonly field = contentChild(TextInput, { descendants: true, read: ElementRef });\n private readonly fieldInput = contentChild(TextInput, { descendants: true });\n\n /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */\n private readonly bottomEdge = signal<number | null>(null);\n /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */\n protected readonly barHeight = signal(0);\n /**\n * The keyboard's height over the dock as native last reported it: the controller's keyboard\n * height, excluding the drag area, or the motion view's cover of the dock.\n */\n private readonly controlled = signal(0);\n\n /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */\n private readonly inset = computed(() => this.safeArea.insets().bottom);\n\n readonly covered = computed(() =>\n this.lifts ? Math.max(0, this.controlled() - this.inset()) : 0,\n );\n\n protected readonly padding = computed(() => {\n if (this.lifts) return this.inset();\n const { height, screenY } = this.keyboard.metrics();\n if (height === 0) return this.inset();\n const edge = this.bottomEdge();\n if (edge === null || screenY === undefined) return height;\n // The edge is in the window, and on Android that can start below the status bar.\n return Math.max(0, edge - (screenY - this.keyboard.windowTop));\n });\n\n /**\n * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field\n * without one is given one.\n */\n protected readonly inputID = computed(() => {\n const input = this.fieldInput();\n const node = this.field()?.nativeElement as HostNode | undefined;\n if (!input || !node) return undefined;\n const own = input.nativeID() ?? input.id();\n if (own) return own;\n const id = `keyboard-dock-${++docks}`;\n untracked(() => this.engine.setProp(node, 'nativeID', id));\n return id;\n });\n\n /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */\n readonly liftRange = computed<ScrollRange>(() => {\n const inset = this.inset();\n return { input: [inset, inset + FAR], output: [0, -FAR] };\n });\n\n private source: HostNode | null = null;\n\n private readonly inFront = inject(SCREEN_IN_FRONT);\n\n constructor() {\n super();\n if (this.lifts) this.liftBar();\n // A root effect, because a covered screen's views are detached from change detection, and a\n // view's own effects wait for it.\n const front = effect(\n () => {\n const inFront = this.inFront();\n untracked(() => {\n if (!inFront && this.keyboard.height() > 0) this.keyboard.dismiss();\n });\n },\n { injector: inject(EnvironmentInjector), manualCleanup: true },\n );\n inject(DestroyRef).onDestroy(() => front.destroy());\n }\n\n /**\n * Move a view with the keyboard on the native side, as the bar is moved: content above the bar\n * that should rise with it. Null when native does not move the bar. See `keyboardLift`.\n */\n lift(view: HostNode, shift: number | null = null): ScrollDrive | null {\n if (!this.lifts) return null;\n const source = this.controllerView();\n const range = this.liftRange();\n return this.engine.driveByEvent(view, source, KEYBOARD_FEED, 'translateY', range, [], shift);\n }\n\n private liftBar(): void {\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const range = this.liftRange();\n untracked(() => {\n if (drive) return drive.update(range);\n const bar = this.barView();\n if (bar) drive = this.lift(bar);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n\n /** The controller's or the motion view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.childList.find((child) => child.nodeKind === 'element') ?? this.node;\n return this.source;\n }\n\n /** The view holding the content, the last of the dock's own. */\n private barView(): HostNode | undefined {\n return this.node.childList.filter((child) => child.nodeKind === 'element').at(-1);\n }\n\n protected onKeyboardMove(event: unknown): void {\n const height = (event as KeyboardMoveEvent).nativeEvent?.height;\n if (height !== undefined) this.controlled.set(height);\n }\n\n protected onBarLayout(event: { nativeEvent?: { layout?: { height?: number } } }): void {\n const height = event.nativeEvent?.layout?.height;\n if (height !== undefined) this.barHeight.set(height);\n }\n\n protected onLayout(): void {\n if (this.lifts) return;\n this.engine.measure(this.node, (frame) => this.bottomEdge.set(frame.y + frame.height));\n }\n}\n\nlet docks = 0;\n\n/**\n * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]=\"dock\">` around a chat's\n * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is\n * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than\n * drawn over what is above. Does nothing where native does not move the bar.\n *\n * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved\n * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view\n * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,\n * and a transform does not change what it thinks.\n */\n@Directive({\n selector: '[keyboardLift]',\n host: { '[style.paddingBottom]': 'padding()' },\n})\nexport class KeyboardLift {\n readonly dock = input.required<KeyboardDock>({ alias: 'keyboardLift' });\n\n protected readonly padding = computed(() => {\n const covered = this.dock().covered();\n return covered > 0 ? covered : undefined;\n });\n\n constructor() {\n const view = inject(ElementRef).nativeElement as HostNode;\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const dock = this.dock();\n const range = dock.liftRange();\n const covered = dock.covered();\n untracked(() => {\n if (!drive) drive = dock.lift(view, covered);\n drive?.update(range);\n drive?.shift(covered);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n}\n"]}
@@ -1,11 +1,13 @@
1
- import type { HostEngine, HostNode } from '@ng-native/fabric';
1
+ import { type HostEngine, type HostNode } from '@ng-native/fabric';
2
2
  export type KeyboardShouldPersistTaps = 'always' | 'never' | 'handled';
3
3
  /**
4
4
  * What a tap in a scrolling container does while a text input has the keyboard up, which is
5
5
  * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):
6
- * a tap anywhere but on the input is the container's before a child sees it, and all it does is
6
+ * a tap anywhere but on a text input is the container's before a child sees it, and all it does is
7
7
  * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.
8
- * `always`: the keyboard stays.
8
+ * `always`: the keyboard stays. A tap on another text input never dismisses, so a form's next
9
+ * field takes the focus with the keyboard still up: React Native's `never` lets any text input's
10
+ * tap through, and with `handled` the text input takes the tap itself.
9
11
  *
10
12
  * Returns the responder's release, for the container's destroy.
11
13
  */
@@ -1,25 +1,19 @@
1
+ import { isTextInput, viewNameOf, } from '@ng-native/fabric';
1
2
  /**
2
3
  * What a tap in a scrolling container does while a text input has the keyboard up, which is
3
4
  * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):
4
- * a tap anywhere but on the input is the container's before a child sees it, and all it does is
5
+ * a tap anywhere but on a text input is the container's before a child sees it, and all it does is
5
6
  * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.
6
- * `always`: the keyboard stays.
7
+ * `always`: the keyboard stays. A tap on another text input never dismisses, so a form's next
8
+ * field takes the focus with the keyboard still up: React Native's `never` lets any text input's
9
+ * tap through, and with `handled` the text input takes the tap itself.
7
10
  *
8
11
  * Returns the responder's release, for the container's destroy.
9
12
  */
10
13
  export function dismissKeyboardOnTap(engine, node, policy) {
11
14
  let dismissing = false;
12
- /** A keyboard is up for an input, and the tap is not on that input. */
13
- const tapDismisses = (target) => {
14
- const focused = engine.focused;
15
- if (!focused)
16
- return false;
17
- for (let at = target; at; at = at.parent) {
18
- if (at === focused)
19
- return false;
20
- }
21
- return true;
22
- };
15
+ /** A keyboard is up for an input, and the tap is not on a text input or above the keyboard. */
16
+ const tapDismisses = (target) => engine.focusedField !== null && !isTextInput(target) && !inAccessoryBar(target);
23
17
  // RN's `_observedScrollSinceBecomingResponder`: a touch that scrolled was a drag, and a drag
24
18
  // through the content is reading it, so its release leaves the keyboard up.
25
19
  const stopScroll = engine.setEventListener(node, 'topScroll', () => {
@@ -35,9 +29,7 @@ export function dismissKeyboardOnTap(engine, node, policy) {
35
29
  if (!dismissing)
36
30
  return;
37
31
  dismissing = false;
38
- const focused = engine.focused;
39
- if (focused)
40
- engine.dispatchCommand(focused, 'blur');
32
+ engine.blurFocused();
41
33
  },
42
34
  onResponderTerminate: () => {
43
35
  dismissing = false;
@@ -48,4 +40,17 @@ export function dismissKeyboardOnTap(engine, node, policy) {
48
40
  stopResponder();
49
41
  };
50
42
  }
43
+ /**
44
+ * Whether the tap is on an `input-accessory-view` or inside one. On iOS the bar is drawn above the
45
+ * keyboard, outside the scroll view it is written in, so a tap on it is not a tap outside the field,
46
+ * as UIKit has it. React Native's `ScrollView.js` dismisses the keyboard for it all the same. On
47
+ * Android the element is a plain view, inside the scroll view on screen too.
48
+ */
49
+ function inAccessoryBar(target) {
50
+ for (let at = target; at; at = at.parent) {
51
+ if (viewNameOf(at) === 'InputAccessoryView')
52
+ return true;
53
+ }
54
+ return false;
55
+ }
51
56
  //# sourceMappingURL=keyboard-taps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-taps.js","sourceRoot":"","sources":["../src/keyboard-taps.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAkB,EAClB,IAAc,EACd,MAAuC;IAEvC,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,uEAAuE;IACvE,MAAM,YAAY,GAAG,CAAC,MAAkB,EAAW,EAAE;QACnD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,KAAK,IAAI,EAAE,GAAsB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;YAC5D,IAAI,EAAE,KAAK,OAAO;gBAAE,OAAO,KAAK,CAAC;QACnC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,6FAA6F;IAC7F,4EAA4E;IAC5E,MAAM,UAAU,GAAG,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;QACjE,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAC9C,gCAAgC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC;QAC7F,yBAAyB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,CAAC;QACxF,gBAAgB,EAAE,GAAG,EAAE;YACrB,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,kBAAkB,EAAE,GAAG,EAAE;YACvB,IAAI,CAAC,UAAU;gBAAE,OAAO;YACxB,UAAU,GAAG,KAAK,CAAC;YACnB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;YAC/B,IAAI,OAAO;gBAAE,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;QACD,oBAAoB,EAAE,GAAG,EAAE;YACzB,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;KACF,CAAC,CAAC;IACH,OAAO,GAAG,EAAE;QACV,UAAU,EAAE,CAAC;QACb,aAAa,EAAE,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { EngineNode, HostEngine, HostNode } from '@ng-native/fabric';\n\nexport type KeyboardShouldPersistTaps = 'always' | 'never' | 'handled';\n\n/**\n * What a tap in a scrolling container does while a text input has the keyboard up, which is\n * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):\n * a tap anywhere but on the input is the container's before a child sees it, and all it does is\n * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.\n * `always`: the keyboard stays.\n *\n * Returns the responder's release, for the container's destroy.\n */\nexport function dismissKeyboardOnTap(\n engine: HostEngine,\n node: HostNode,\n policy: () => KeyboardShouldPersistTaps,\n): () => void {\n let dismissing = false;\n\n /** A keyboard is up for an input, and the tap is not on that input. */\n const tapDismisses = (target: EngineNode): boolean => {\n const focused = engine.focused;\n if (!focused) return false;\n for (let at: EngineNode | null = target; at; at = at.parent) {\n if (at === focused) return false;\n }\n return true;\n };\n\n // RN's `_observedScrollSinceBecomingResponder`: a touch that scrolled was a drag, and a drag\n // through the content is reading it, so its release leaves the keyboard up.\n const stopScroll = engine.setEventListener(node, 'topScroll', () => {\n dismissing = false;\n });\n const stopResponder = engine.setResponder(node, {\n onStartShouldSetResponderCapture: (_, target) => policy() === 'never' && tapDismisses(target),\n onStartShouldSetResponder: (_, target) => policy() === 'handled' && tapDismisses(target),\n onResponderGrant: () => {\n dismissing = true;\n },\n onResponderRelease: () => {\n if (!dismissing) return;\n dismissing = false;\n const focused = engine.focused;\n if (focused) engine.dispatchCommand(focused, 'blur');\n },\n onResponderTerminate: () => {\n dismissing = false;\n },\n });\n return () => {\n stopScroll();\n stopResponder();\n };\n}\n"]}
1
+ {"version":3,"file":"keyboard-taps.js","sourceRoot":"","sources":["../src/keyboard-taps.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,UAAU,GAIX,MAAM,mBAAmB,CAAC;AAI3B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAkB,EAClB,IAAc,EACd,MAAuC;IAEvC,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,+FAA+F;IAC/F,MAAM,YAAY,GAAG,CAAC,MAAkB,EAAW,EAAE,CACnD,MAAM,CAAC,YAAY,KAAK,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAElF,6FAA6F;IAC7F,4EAA4E;IAC5E,MAAM,UAAU,GAAG,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;QACjE,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAC9C,gCAAgC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC;QAC7F,yBAAyB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,CAAC;QACxF,gBAAgB,EAAE,GAAG,EAAE;YACrB,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,kBAAkB,EAAE,GAAG,EAAE;YACvB,IAAI,CAAC,UAAU;gBAAE,OAAO;YACxB,UAAU,GAAG,KAAK,CAAC;YACnB,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;QACD,oBAAoB,EAAE,GAAG,EAAE;YACzB,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;KACF,CAAC,CAAC;IACH,OAAO,GAAG,EAAE;QACV,UAAU,EAAE,CAAC;QACb,aAAa,EAAE,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,MAAkB;IACxC,KAAK,IAAI,EAAE,GAAsB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;QAC5D,IAAI,UAAU,CAAC,EAAE,CAAC,KAAK,oBAAoB;YAAE,OAAO,IAAI,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import {\n isTextInput,\n viewNameOf,\n type EngineNode,\n type HostEngine,\n type HostNode,\n} from '@ng-native/fabric';\n\nexport type KeyboardShouldPersistTaps = 'always' | 'never' | 'handled';\n\n/**\n * What a tap in a scrolling container does while a text input has the keyboard up, which is\n * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):\n * a tap anywhere but on a text input is the container's before a child sees it, and all it does is\n * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.\n * `always`: the keyboard stays. A tap on another text input never dismisses, so a form's next\n * field takes the focus with the keyboard still up: React Native's `never` lets any text input's\n * tap through, and with `handled` the text input takes the tap itself.\n *\n * Returns the responder's release, for the container's destroy.\n */\nexport function dismissKeyboardOnTap(\n engine: HostEngine,\n node: HostNode,\n policy: () => KeyboardShouldPersistTaps,\n): () => void {\n let dismissing = false;\n\n /** A keyboard is up for an input, and the tap is not on a text input or above the keyboard. */\n const tapDismisses = (target: EngineNode): boolean =>\n engine.focusedField !== null && !isTextInput(target) && !inAccessoryBar(target);\n\n // RN's `_observedScrollSinceBecomingResponder`: a touch that scrolled was a drag, and a drag\n // through the content is reading it, so its release leaves the keyboard up.\n const stopScroll = engine.setEventListener(node, 'topScroll', () => {\n dismissing = false;\n });\n const stopResponder = engine.setResponder(node, {\n onStartShouldSetResponderCapture: (_, target) => policy() === 'never' && tapDismisses(target),\n onStartShouldSetResponder: (_, target) => policy() === 'handled' && tapDismisses(target),\n onResponderGrant: () => {\n dismissing = true;\n },\n onResponderRelease: () => {\n if (!dismissing) return;\n dismissing = false;\n engine.blurFocused();\n },\n onResponderTerminate: () => {\n dismissing = false;\n },\n });\n return () => {\n stopScroll();\n stopResponder();\n };\n}\n\n/**\n * Whether the tap is on an `input-accessory-view` or inside one. On iOS the bar is drawn above the\n * keyboard, outside the scroll view it is written in, so a tap on it is not a tap outside the field,\n * as UIKit has it. React Native's `ScrollView.js` dismisses the keyboard for it all the same. On\n * Android the element is a plain view, inside the scroll view on screen too.\n */\nfunction inAccessoryBar(target: EngineNode): boolean {\n for (let at: EngineNode | null = target; at; at = at.parent) {\n if (viewNameOf(at) === 'InputAccessoryView') return true;\n }\n return false;\n}\n"]}
@@ -21,7 +21,7 @@
21
21
  * is the same reason it has no header. Give a modal a provider of its own with
22
22
  * `[reportInsets]="false"`, so its geometry stays out of the app-wide `SafeArea` while it is up.
23
23
  */
24
- import { Component, DestroyRef, ElementRef, Renderer2, booleanAttribute, inject, input, } from '@angular/core';
24
+ import { ApplicationRef, Component, DestroyRef, ElementRef, Renderer2, booleanAttribute, inject, input, } from '@angular/core';
25
25
  import { SafeArea } from '@ng-native/device';
26
26
  import { Engine, claimHost } from '@ng-native/fabric';
27
27
  import { registerSafeAreaComponents } from "./safe-area.js";
@@ -47,16 +47,25 @@ export class SafeAreaProvider {
47
47
  claimHost(host);
48
48
  const area = inject(SafeArea);
49
49
  const engine = inject(Engine, { optional: true });
50
+ const app = inject(ApplicationRef);
50
51
  const stop = inject(Renderer2).listen(host, 'insetsChange', (event) => {
51
52
  const change = event?.nativeEvent;
52
53
  if (!change || !this.reportInsets())
53
54
  return;
54
- area.report(change.insets, change.frame);
55
+ area.report(change.insets, change.frame, this);
55
56
  // Also published to the stylesheet, where a padding that clears the home indicator
56
57
  // belongs. `env()` on the web; a seeded custom property here.
57
58
  engine?.updateTokens(insetTokens(change.insets));
58
59
  });
59
- inject(DestroyRef).onDestroy(stop);
60
+ inject(DestroyRef).onDestroy(() => {
61
+ stop();
62
+ // The whole app going takes `SafeArea` and the stylesheet with it: a restyle and a commit
63
+ // of a tree on its way out would be all this did.
64
+ if (app.injector.destroyed)
65
+ return;
66
+ if (area.withdraw(this))
67
+ engine?.updateTokens(insetTokens(area.insets()));
68
+ });
60
69
  }
61
70
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SafeAreaProvider, deps: [], target: i0.ɵɵFactoryTarget.Component });
62
71
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: SafeAreaProvider, isStandalone: true, selector: "safe-area-provider", inputs: { reportInsets: { classPropertyName: "reportInsets", publicName: "reportInsets", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '<ng-content />', isInline: true });
@@ -1 +1 @@
1
- {"version":3,"file":"safe-area-provider.js","sourceRoot":"","sources":["../src/safe-area-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,KAAK,GACN,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAA2B,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAiB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;;AAE5D,4FAA4F;AAC5F,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO;QACL,uBAAuB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE;QAC/C,yBAAyB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;QACnD,0BAA0B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QACrD,wBAAwB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KAClD,CAAC;AACJ,CAAC;AAWD,MAAM,OAAO,gBAAgB;IAC3B;;;OAGG;IACM,YAAY,GAAG,KAAK,CAAC,IAAI,oFAAI,SAAS,EAAE,gBAAgB,GAAG,CAAC;IAErE;QACE,0BAA0B,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;QAC1D,SAAS,CAAC,IAAI,CAAC,CAAC;QAEhB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CACnC,IAAI,EACJ,cAAc,EACd,CAAC,KAAqC,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,KAAK,EAAE,WAAW,CAAC;YAClC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;gBAAE,OAAO;YAC5C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACzC,mFAAmF;YACnF,8DAA8D;YAC9D,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC,CACF,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;uGA3BU,gBAAgB;2FAAhB,gBAAgB,uOAFjB,gBAAgB;;2FAEf,gBAAgB;kBAJ5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,oBAAoB;oBAC9B,QAAQ,EAAE,gBAAgB;iBAC3B","sourcesContent":["/**\n * The view that measures the safe area and reports it, for the layouts that need the numbers.\n *\n * ```html\n * <safe-area-provider>\n * <native-stack-outlet />\n * </safe-area-provider>\n * ```\n *\n * One, at the root, wrapping everything. What it measures is its own frame and the insets around\n * it, which is why it has to be the thing filling the window rather than a view somewhere inside\n * it. It fills by default and paints nothing.\n *\n * **A `<safe-area-view>` needs one above it**, which is less obvious than it sounds. The native\n * view walks its superviews for a provider and falls back to reading its own insets - but only\n * once, as it enters the window, before it has been laid out, and nothing notifies it when they\n * change. With no provider it therefore insets by zero and looks like it is working, until a later\n * update to that view happens to re-read them and the content jumps.\n *\n * That is also why a presented screen needs its own: it is outside the stack's view tree, which\n * is the same reason it has no header. Give a modal a provider of its own with\n * `[reportInsets]=\"false\"`, so its geometry stays out of the app-wide `SafeArea` while it is up.\n */\nimport {\n Component,\n DestroyRef,\n ElementRef,\n Renderer2,\n booleanAttribute,\n inject,\n input,\n} from '@angular/core';\nimport { SafeArea, type Frame, type Insets } from '@ng-native/device';\nimport { Engine, claimHost, type HostNode } from '@ng-native/fabric';\nimport { registerSafeAreaComponents } from './safe-area.ts';\n\n/** The insets, in the form the cascade carries: one custom property per edge, in points. */\nfunction insetTokens(insets: Insets): Record<string, { length: number }> {\n return {\n '--safe-area-inset-top': { length: insets.top },\n '--safe-area-inset-right': { length: insets.right },\n '--safe-area-inset-bottom': { length: insets.bottom },\n '--safe-area-inset-left': { length: insets.left },\n };\n}\n\ninterface InsetsChange {\n readonly insets: Insets;\n readonly frame: Frame;\n}\n\n@Component({\n selector: 'safe-area-provider',\n template: '<ng-content />',\n})\nexport class SafeAreaProvider {\n /**\n * Whether what this measures becomes `SafeArea.insets`. The app has one safe area; a second\n * provider inside a modal is there so native descendants can find one, not to redefine it.\n */\n readonly reportInsets = input(true, { transform: booleanAttribute });\n\n constructor() {\n registerSafeAreaComponents();\n const host = inject(ElementRef).nativeElement as HostNode;\n claimHost(host);\n\n const area = inject(SafeArea);\n const engine = inject(Engine, { optional: true });\n const stop = inject(Renderer2).listen(\n host,\n 'insetsChange',\n (event: { nativeEvent?: InsetsChange }) => {\n const change = event?.nativeEvent;\n if (!change || !this.reportInsets()) return;\n area.report(change.insets, change.frame);\n // Also published to the stylesheet, where a padding that clears the home indicator\n // belongs. `env()` on the web; a seeded custom property here.\n engine?.updateTokens(insetTokens(change.insets));\n },\n );\n inject(DestroyRef).onDestroy(stop);\n }\n}\n"]}
1
+ {"version":3,"file":"safe-area-provider.js","sourceRoot":"","sources":["../src/safe-area-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EACL,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,KAAK,GACN,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAA2B,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAiB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;;AAE5D,4FAA4F;AAC5F,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO;QACL,uBAAuB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE;QAC/C,yBAAyB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;QACnD,0BAA0B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QACrD,wBAAwB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KAClD,CAAC;AACJ,CAAC;AAWD,MAAM,OAAO,gBAAgB;IAC3B;;;OAGG;IACM,YAAY,GAAG,KAAK,CAAC,IAAI,oFAAI,SAAS,EAAE,gBAAgB,GAAG,CAAC;IAErE;QACE,0BAA0B,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;QAC1D,SAAS,CAAC,IAAI,CAAC,CAAC;QAEhB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CACnC,IAAI,EACJ,cAAc,EACd,CAAC,KAAqC,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,KAAK,EAAE,WAAW,CAAC;YAClC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;gBAAE,OAAO;YAC5C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC/C,mFAAmF;YACnF,8DAA8D;YAC9D,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC,CACF,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YAChC,IAAI,EAAE,CAAC;YACP,0FAA0F;YAC1F,kDAAkD;YAClD,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS;gBAAE,OAAO;YACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;uGAlCU,gBAAgB;2FAAhB,gBAAgB,uOAFjB,gBAAgB;;2FAEf,gBAAgB;kBAJ5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,oBAAoB;oBAC9B,QAAQ,EAAE,gBAAgB;iBAC3B","sourcesContent":["/**\n * The view that measures the safe area and reports it, for the layouts that need the numbers.\n *\n * ```html\n * <safe-area-provider>\n * <native-stack-outlet />\n * </safe-area-provider>\n * ```\n *\n * One, at the root, wrapping everything. What it measures is its own frame and the insets around\n * it, which is why it has to be the thing filling the window rather than a view somewhere inside\n * it. It fills by default and paints nothing.\n *\n * **A `<safe-area-view>` needs one above it**, which is less obvious than it sounds. The native\n * view walks its superviews for a provider and falls back to reading its own insets - but only\n * once, as it enters the window, before it has been laid out, and nothing notifies it when they\n * change. With no provider it therefore insets by zero and looks like it is working, until a later\n * update to that view happens to re-read them and the content jumps.\n *\n * That is also why a presented screen needs its own: it is outside the stack's view tree, which\n * is the same reason it has no header. Give a modal a provider of its own with\n * `[reportInsets]=\"false\"`, so its geometry stays out of the app-wide `SafeArea` while it is up.\n */\nimport {\n ApplicationRef,\n Component,\n DestroyRef,\n ElementRef,\n Renderer2,\n booleanAttribute,\n inject,\n input,\n} from '@angular/core';\nimport { SafeArea, type Frame, type Insets } from '@ng-native/device';\nimport { Engine, claimHost, type HostNode } from '@ng-native/fabric';\nimport { registerSafeAreaComponents } from './safe-area.ts';\n\n/** The insets, in the form the cascade carries: one custom property per edge, in points. */\nfunction insetTokens(insets: Insets): Record<string, { length: number }> {\n return {\n '--safe-area-inset-top': { length: insets.top },\n '--safe-area-inset-right': { length: insets.right },\n '--safe-area-inset-bottom': { length: insets.bottom },\n '--safe-area-inset-left': { length: insets.left },\n };\n}\n\ninterface InsetsChange {\n readonly insets: Insets;\n readonly frame: Frame;\n}\n\n@Component({\n selector: 'safe-area-provider',\n template: '<ng-content />',\n})\nexport class SafeAreaProvider {\n /**\n * Whether what this measures becomes `SafeArea.insets`. The app has one safe area; a second\n * provider inside a modal is there so native descendants can find one, not to redefine it.\n */\n readonly reportInsets = input(true, { transform: booleanAttribute });\n\n constructor() {\n registerSafeAreaComponents();\n const host = inject(ElementRef).nativeElement as HostNode;\n claimHost(host);\n\n const area = inject(SafeArea);\n const engine = inject(Engine, { optional: true });\n const app = inject(ApplicationRef);\n const stop = inject(Renderer2).listen(\n host,\n 'insetsChange',\n (event: { nativeEvent?: InsetsChange }) => {\n const change = event?.nativeEvent;\n if (!change || !this.reportInsets()) return;\n area.report(change.insets, change.frame, this);\n // Also published to the stylesheet, where a padding that clears the home indicator\n // belongs. `env()` on the web; a seeded custom property here.\n engine?.updateTokens(insetTokens(change.insets));\n },\n );\n inject(DestroyRef).onDestroy(() => {\n stop();\n // The whole app going takes `SafeArea` and the stylesheet with it: a restyle and a commit\n // of a tree on its way out would be all this did.\n if (app.injector.destroyed) return;\n if (area.withdraw(this)) engine?.updateTokens(insetTokens(area.insets()));\n });\n }\n}\n"]}
@@ -25,7 +25,7 @@ export declare abstract class ScrollViewProps extends ViewBase {
25
25
  /** Stop only at these offsets. */
26
26
  readonly snapToOffsets: import("@angular/core").InputSignal<readonly number[] | undefined>;
27
27
  /** Where a snap point sits in the viewport. */
28
- readonly snapToAlignment: import("@angular/core").InputSignal<"center" | "start" | "end" | undefined>;
28
+ readonly snapToAlignment: import("@angular/core").InputSignal<"center" | "end" | "start" | undefined>;
29
29
  /** Whether the start of the content is a snap point. Defaults to true. */
30
30
  readonly snapToStart: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
31
31
  /** Whether the end of the content is a snap point. Defaults to true. */
@@ -0,0 +1,20 @@
1
+ import { type AnimatedValue, type CompositeAnimation, type SpringConfig, type TimingConfig } from '../animated.ts';
2
+ import { AnimatedStyleBase } from '../animated-style.ts';
3
+ import * as i0 from "@angular/core";
4
+ export { Easing } from '../easing.ts';
5
+ export declare const Animated: {
6
+ timing: (value: AnimatedValue, config: TimingConfig) => CompositeAnimation;
7
+ spring: (value: AnimatedValue, config: SpringConfig) => CompositeAnimation;
8
+ delay: (_time: number) => CompositeAnimation;
9
+ Value: typeof AnimatedValue;
10
+ Node: typeof import("../animated.ts").AnimatedNode;
11
+ Interpolation: typeof import("../animated.ts").AnimatedInterpolation;
12
+ sequence: (animations: readonly CompositeAnimation[]) => CompositeAnimation;
13
+ parallel: (animations: readonly CompositeAnimation[], config?: {
14
+ stopTogether?: boolean;
15
+ }) => CompositeAnimation;
16
+ };
17
+ export declare class AnimatedStyle extends AnimatedStyleBase {
18
+ static ɵfac: i0.ɵɵFactoryDeclaration<AnimatedStyle, never>;
19
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AnimatedStyle, "[animatedStyle]", never, {}, {}, never, never, true, never>;
20
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `@ng-native/components/animations` where React Native's animation graph cannot load: a test,
3
+ * which `ngNative()` resolves the entry point from to here.
4
+ *
5
+ * The real entry point reaches `react-native/Libraries/Animated`, which is Flow source that Node
6
+ * cannot parse. This is the browser build's graph (`animated.ts`) with no frames between: an
7
+ * animation finishes the moment it starts, its value is where it ends, and its callback is called
8
+ * with `finished: true` at once, so a test reads the style an animation lands on. `timing`,
9
+ * `spring` and `delay` are instant; `sequence` and `parallel` compose them as they always do.
10
+ */
11
+ import { Directive } from '@angular/core';
12
+ import { Animated as graph, animationBackend, } from "../animated.js";
13
+ import { AnimatedStyleBase } from "../animated-style.js";
14
+ import { ANIMATION } from "../animation.js";
15
+ import * as i0 from "@angular/core";
16
+ export { Easing } from "../easing.js";
17
+ /** A zero duration with no delay is the one timing the graph ends without asking for a frame. */
18
+ const instant = (toValue) => ({ toValue, duration: 0, delay: 0 });
19
+ export const Animated = {
20
+ ...graph,
21
+ timing: (value, config) => graph.timing(value, instant(config.toValue)),
22
+ spring: (value, config) => graph.timing(value, instant(config.toValue)),
23
+ delay: (_time) => graph.delay(0),
24
+ };
25
+ export class AnimatedStyle extends AnimatedStyleBase {
26
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: AnimatedStyle, deps: null, target: i0.ɵɵFactoryTarget.Directive });
27
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.2.0", type: AnimatedStyle, isStandalone: true, selector: "[animatedStyle]", providers: [{ provide: ANIMATION, useValue: animationBackend }], usesInheritance: true, ngImport: i0 });
28
+ }
29
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: AnimatedStyle, decorators: [{
30
+ type: Directive,
31
+ args: [{
32
+ selector: '[animatedStyle]',
33
+ providers: [{ provide: ANIMATION, useValue: animationBackend }],
34
+ }]
35
+ }] });
36
+ //# sourceMappingURL=animations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animations.js","sourceRoot":"","sources":["../../src/stand-ins/animations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EACL,QAAQ,IAAI,KAAK,EACjB,gBAAgB,GAKjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,iGAAiG;AACjG,MAAM,OAAO,GAAG,CAAC,OAAe,EAAgB,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AAExF,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,GAAG,KAAK;IACR,MAAM,EAAE,CAAC,KAAoB,EAAE,MAAoB,EAAsB,EAAE,CACzE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,KAAoB,EAAE,MAAoB,EAAsB,EAAE,CACzE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,EAAE,CAAC,KAAa,EAAsB,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;CAC7D,CAAC;AAMF,MAAM,OAAO,aAAc,SAAQ,iBAAiB;uGAAvC,aAAa;2FAAb,aAAa,8DAFb,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;;2FAEpD,aAAa;kBAJzB,SAAS;mBAAC;oBACT,QAAQ,EAAE,iBAAiB;oBAC3B,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;iBAChE","sourcesContent":["/**\n * `@ng-native/components/animations` where React Native's animation graph cannot load: a test,\n * which `ngNative()` resolves the entry point from to here.\n *\n * The real entry point reaches `react-native/Libraries/Animated`, which is Flow source that Node\n * cannot parse. This is the browser build's graph (`animated.ts`) with no frames between: an\n * animation finishes the moment it starts, its value is where it ends, and its callback is called\n * with `finished: true` at once, so a test reads the style an animation lands on. `timing`,\n * `spring` and `delay` are instant; `sequence` and `parallel` compose them as they always do.\n */\nimport { Directive } from '@angular/core';\nimport {\n Animated as graph,\n animationBackend,\n type AnimatedValue,\n type CompositeAnimation,\n type SpringConfig,\n type TimingConfig,\n} from '../animated.ts';\nimport { AnimatedStyleBase } from '../animated-style.ts';\nimport { ANIMATION } from '../animation.ts';\n\nexport { Easing } from '../easing.ts';\n\n/** A zero duration with no delay is the one timing the graph ends without asking for a frame. */\nconst instant = (toValue: number): TimingConfig => ({ toValue, duration: 0, delay: 0 });\n\nexport const Animated = {\n ...graph,\n timing: (value: AnimatedValue, config: TimingConfig): CompositeAnimation =>\n graph.timing(value, instant(config.toValue)),\n spring: (value: AnimatedValue, config: SpringConfig): CompositeAnimation =>\n graph.timing(value, instant(config.toValue)),\n delay: (_time: number): CompositeAnimation => graph.delay(0),\n};\n\n@Directive({\n selector: '[animatedStyle]',\n providers: [{ provide: ANIMATION, useValue: animationBackend }],\n})\nexport class AnimatedStyle extends AnimatedStyleBase {}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { type SimpleChanges } from '@angular/core';
2
2
  import type { NativeSyntheticEvent } from '@ng-native/fabric';
3
- import type { TextInputChangePayload } from './events.ts';
3
+ import type { SelectionChangePayload, TextInputChangePayload } from './events.ts';
4
4
  import { ViewBase } from './view-base.ts';
5
5
  import * as i0 from "@angular/core";
6
6
  export type KeyboardType = 'default' | 'number-pad' | 'decimal-pad' | 'numeric' | 'email-address' | 'phone-pad' | 'url' | 'ascii-capable' | 'numbers-and-punctuation' | 'name-phone-pad' | 'twitter' | 'web-search' | 'visible-password';
@@ -79,7 +79,10 @@ export declare class TextInput extends ViewBase {
79
79
  * as a `<textarea rows>` is. Read by the engine, which sizes the field; never sent to native.
80
80
  */
81
81
  readonly rows: import("@angular/core").InputSignalWithTransform<number | undefined, unknown>;
82
- /** The selected range, or the caret when `start` equals `end`. */
82
+ /**
83
+ * The selected range, or the caret when `end` is left out or equals `start`. Sent to native
84
+ * whenever it differs from what native last had, so a caret the user moves goes back to it.
85
+ */
83
86
  readonly selection: import("@angular/core").InputSignal<{
84
87
  readonly start: number;
85
88
  readonly end?: number;
@@ -125,9 +128,9 @@ export declare class TextInput extends ViewBase {
125
128
  * Ignored on iOS, where the native field has no such chrome.
126
129
  */
127
130
  readonly underlineColorAndroid: import("@angular/core").InputSignal<string>;
128
- /** Android: the caret colour. */
131
+ /** Android: the caret colour. Follows `selectionColor` by default, as RN's does. */
129
132
  readonly cursorColor: import("@angular/core").InputSignal<string | undefined>;
130
- /** Android: the selection handle colour. */
133
+ /** Android: the selection handle colour. Follows `selectionColor` by default, as RN's does. */
131
134
  readonly selectionHandleColor: import("@angular/core").InputSignal<string | undefined>;
132
135
  /** Android: where text sits in a tall field. */
133
136
  readonly textAlignVertical: import("@angular/core").InputSignal<"auto" | "top" | "bottom" | "center" | undefined>;
@@ -143,12 +146,26 @@ export declare class TextInput extends ViewBase {
143
146
  /** Android: the line-breaking strategy. */
144
147
  readonly textBreakStrategy: import("@angular/core").InputSignal<"simple" | "highQuality" | "balanced" | undefined>;
145
148
  protected readonly eventCount: import("@angular/core").WritableSignal<number>;
149
+ /** RN's normalised selection: an `end` left out is `start`, where iOS would read 0. */
150
+ protected readonly resolvedSelection: import("@angular/core").Signal<{
151
+ start: number;
152
+ end: number;
153
+ } | undefined>;
154
+ protected readonly resolvedCursorColor: import("@angular/core").Signal<string | undefined>;
155
+ protected readonly resolvedSelectionHandleColor: import("@angular/core").Signal<string | undefined>;
146
156
  protected readonly resolvedEditable: import("@angular/core").Signal<boolean | undefined>;
147
157
  protected disabledForAccessibility(): boolean | undefined;
148
158
  protected readonly resolvedSubmitBehavior: import("@angular/core").Signal<SubmitBehavior>;
149
159
  private readonly keyboard;
150
160
  /** What native last reported, so a value set from code can be told from an echo. */
151
161
  private lastNativeText;
162
+ /**
163
+ * The selection native last had, sent or reported, so a bound one is sent only when it differs.
164
+ * Unset to start with, so the first bound selection goes out too.
165
+ */
166
+ private lastSelection;
167
+ /** Moved on every reported selection, so the bound one is compared with it again. */
168
+ private readonly selectionReports;
152
169
  private readonly control;
153
170
  constructor();
154
171
  ngOnChanges(changes: SimpleChanges): void;
@@ -168,6 +185,16 @@ export declare class TextInput extends ViewBase {
168
185
  /** Whether this field currently has the keyboard. */
169
186
  isFocused(): boolean;
170
187
  protected onNativeChange(event: NativeSyntheticEvent<Partial<TextInputChangePayload>>): void;
188
+ /** Send native the text and selection it does not have, as one `setTextAndSelection`. */
189
+ private syncWithNative;
190
+ /**
191
+ * Whether a bound selection is not the one native last had. The prop alone does not move the
192
+ * caret: Android has no `selection` prop, and iOS reads it only as the field appears or its
193
+ * text is replaced.
194
+ */
195
+ private differsFromNative;
196
+ /** What native now has selected, which a bound selection that differs is sent over again. */
197
+ protected onNativeSelectionChange(event: NativeSyntheticEvent<Partial<SelectionChangePayload>>): void;
171
198
  static ɵfac: i0.ɵɵFactoryDeclaration<TextInput, never>;
172
199
  static ɵdir: i0.ɵɵDirectiveDeclaration<TextInput, "text-input", ["textInput"], { "value": { "alias": "value"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "invalid": { "alias": "invalid"; "required": false; "isSignal": true; }; "touched": { "alias": "touched"; "required": false; "isSignal": true; }; "multiline": { "alias": "multiline"; "required": false; "isSignal": true; }; "scrollEnabled": { "alias": "scrollEnabled"; "required": false; "isSignal": true; }; "editable": { "alias": "editable"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "placeholderTextColor": { "alias": "placeholderTextColor"; "required": false; "isSignal": true; }; "keyboardType": { "alias": "keyboardType"; "required": false; "isSignal": true; }; "returnKeyType": { "alias": "returnKeyType"; "required": false; "isSignal": true; }; "submitBehavior": { "alias": "submitBehavior"; "required": false; "isSignal": true; }; "secureTextEntry": { "alias": "secureTextEntry"; "required": false; "isSignal": true; }; "autoCapitalize": { "alias": "autoCapitalize"; "required": false; "isSignal": true; }; "autoCorrect": { "alias": "autoCorrect"; "required": false; "isSignal": true; }; "autoFocus": { "alias": "autoFocus"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "numberOfLines": { "alias": "numberOfLines"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "selection": { "alias": "selection"; "required": false; "isSignal": true; }; "selectionColor": { "alias": "selectionColor"; "required": false; "isSignal": true; }; "selectTextOnFocus": { "alias": "selectTextOnFocus"; "required": false; "isSignal": true; }; "caretHidden": { "alias": "caretHidden"; "required": false; "isSignal": true; }; "contextMenuHidden": { "alias": "contextMenuHidden"; "required": false; "isSignal": true; }; "textAlign": { "alias": "textAlign"; "required": false; "isSignal": true; }; "allowFontScaling": { "alias": "allowFontScaling"; "required": false; "isSignal": true; }; "maxFontSizeMultiplier": { "alias": "maxFontSizeMultiplier"; "required": false; "isSignal": true; }; "inputAccessoryViewID": { "alias": "inputAccessoryViewID"; "required": false; "isSignal": true; }; "clearButtonMode": { "alias": "clearButtonMode"; "required": false; "isSignal": true; }; "clearTextOnFocus": { "alias": "clearTextOnFocus"; "required": false; "isSignal": true; }; "enablesReturnKeyAutomatically": { "alias": "enablesReturnKeyAutomatically"; "required": false; "isSignal": true; }; "keyboardAppearance": { "alias": "keyboardAppearance"; "required": false; "isSignal": true; }; "passwordRules": { "alias": "passwordRules"; "required": false; "isSignal": true; }; "spellCheck": { "alias": "spellCheck"; "required": false; "isSignal": true; }; "smartInsertDelete": { "alias": "smartInsertDelete"; "required": false; "isSignal": true; }; "dataDetectorTypes": { "alias": "dataDetectorTypes"; "required": false; "isSignal": true; }; "textContentType": { "alias": "textContentType"; "required": false; "isSignal": true; }; "autoComplete": { "alias": "autoComplete"; "required": false; "isSignal": true; }; "underlineColorAndroid": { "alias": "underlineColorAndroid"; "required": false; "isSignal": true; }; "cursorColor": { "alias": "cursorColor"; "required": false; "isSignal": true; }; "selectionHandleColor": { "alias": "selectionHandleColor"; "required": false; "isSignal": true; }; "textAlignVertical": { "alias": "textAlignVertical"; "required": false; "isSignal": true; }; "importantForAutofill": { "alias": "importantForAutofill"; "required": false; "isSignal": true; }; "showSoftInputOnFocus": { "alias": "showSoftInputOnFocus"; "required": false; "isSignal": true; }; "disableFullscreenUI": { "alias": "disableFullscreenUI"; "required": false; "isSignal": true; }; "inlineImageLeft": { "alias": "inlineImageLeft"; "required": false; "isSignal": true; }; "inlineImagePadding": { "alias": "inlineImagePadding"; "required": false; "isSignal": true; }; "textBreakStrategy": { "alias": "textBreakStrategy"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "changeText": "changeText"; "touch": "touch"; }, never, never, true, never>;
173
200
  }
@@ -84,7 +84,10 @@ export class TextInput extends ViewBase {
84
84
  * as a `<textarea rows>` is. Read by the engine, which sizes the field; never sent to native.
85
85
  */
86
86
  rows = input(undefined, { ...(ngDevMode ? { debugName: "rows" } : /* istanbul ignore next */ {}), transform: optionalNumber });
87
- /** The selected range, or the caret when `start` equals `end`. */
87
+ /**
88
+ * The selected range, or the caret when `end` is left out or equals `start`. Sent to native
89
+ * whenever it differs from what native last had, so a caret the user moves goes back to it.
90
+ */
88
91
  selection = input(/* @ts-ignore */
89
92
  ...(ngDevMode ? [undefined, { debugName: "selection" }] : /* istanbul ignore next */ []));
90
93
  selectionColor = input(/* @ts-ignore */
@@ -138,10 +141,10 @@ export class TextInput extends ViewBase {
138
141
  */
139
142
  underlineColorAndroid = input('transparent', /* @ts-ignore */
140
143
  ...(ngDevMode ? [{ debugName: "underlineColorAndroid" }] : /* istanbul ignore next */ []));
141
- /** Android: the caret colour. */
144
+ /** Android: the caret colour. Follows `selectionColor` by default, as RN's does. */
142
145
  cursorColor = input(/* @ts-ignore */
143
146
  ...(ngDevMode ? [undefined, { debugName: "cursorColor" }] : /* istanbul ignore next */ []));
144
- /** Android: the selection handle colour. */
147
+ /** Android: the selection handle colour. Follows `selectionColor` by default, as RN's does. */
145
148
  selectionHandleColor = input(/* @ts-ignore */
146
149
  ...(ngDevMode ? [undefined, { debugName: "selectionHandleColor" }] : /* istanbul ignore next */ []));
147
150
  /** Android: where text sits in a tall field. */
@@ -163,6 +166,25 @@ export class TextInput extends ViewBase {
163
166
  ...(ngDevMode ? [undefined, { debugName: "textBreakStrategy" }] : /* istanbul ignore next */ []));
164
167
  eventCount = signal(0, /* @ts-ignore */
165
168
  ...(ngDevMode ? [{ debugName: "eventCount" }] : /* istanbul ignore next */ []));
169
+ /** RN's normalised selection: an `end` left out is `start`, where iOS would read 0. */
170
+ resolvedSelection = computed(() => {
171
+ const selection = this.selection();
172
+ return selection
173
+ ? { start: selection.start, end: selection.end ?? selection.start }
174
+ : undefined;
175
+ }, /* @ts-ignore */
176
+ ...(ngDevMode ? [{ debugName: "resolvedSelection" }] : /* istanbul ignore next */ []));
177
+ // RN's Android defaults, checked against `undefined` as RN does rather than with `??`.
178
+ resolvedCursorColor = computed(() => {
179
+ const color = this.cursorColor();
180
+ return color === undefined ? this.selectionColor() : color;
181
+ }, /* @ts-ignore */
182
+ ...(ngDevMode ? [{ debugName: "resolvedCursorColor" }] : /* istanbul ignore next */ []));
183
+ resolvedSelectionHandleColor = computed(() => {
184
+ const color = this.selectionHandleColor();
185
+ return color === undefined ? this.selectionColor() : color;
186
+ }, /* @ts-ignore */
187
+ ...(ngDevMode ? [{ debugName: "resolvedSelectionHandleColor" }] : /* istanbul ignore next */ []));
166
188
  resolvedEditable = computed(() => this.disabled() || this.readonly() ? false : this.editable(), /* @ts-ignore */
167
189
  ...(ngDevMode ? [{ debugName: "resolvedEditable" }] : /* istanbul ignore next */ []));
168
190
  disabledForAccessibility() {
@@ -179,27 +201,22 @@ export class TextInput extends ViewBase {
179
201
  keyboard = inject(Keyboard);
180
202
  /** What native last reported, so a value set from code can be told from an echo. */
181
203
  lastNativeText = null;
204
+ /**
205
+ * The selection native last had, sent or reported, so a bound one is sent only when it differs.
206
+ * Unset to start with, so the first bound selection goes out too.
207
+ */
208
+ lastSelection = { start: -1, end: -1 };
209
+ /** Moved on every reported selection, so the bound one is compared with it again. */
210
+ selectionReports = signal(0, /* @ts-ignore */
211
+ ...(ngDevMode ? [{ debugName: "selectionReports" }] : /* istanbul ignore next */ []));
182
212
  control = new ControlledModel(this.value, 'value');
183
213
  constructor() {
184
214
  super();
185
- // RN's layout effect, keyed as RN keys it: the value, and the count every native change
186
- // moves on, so a refusal that leaves the value where it was is compared all the same. After
187
- // render rather than as an ordinary effect, because an accepted edit only reaches the model
188
- // during change detection, and comparing before it has would send the old text back.
189
- afterRenderEffect({
190
- write: () => {
191
- const desired = this.value();
192
- const count = this.eventCount();
193
- if (this.lastNativeText === null || this.lastNativeText === desired)
194
- return;
195
- // The `text` prop alone may be ignored as stale, so send it as a command with the count
196
- // native expects.
197
- this.lastNativeText = desired;
198
- this.engine.dispatchCommand(this.node, 'setTextAndSelection', [count, desired, -1, -1]);
199
- // Fabric sized the field for the text it had; see `HostEngine.remeasure`.
200
- this.engine.remeasure(this.node);
201
- },
202
- });
215
+ // RN's layout effect, keyed as RN keys it: the value, the selection, and the count every
216
+ // native change moves on, so a refusal that leaves the value where it was is compared all the
217
+ // same. After render rather than as an ordinary effect, because an accepted edit only reaches
218
+ // the model during change detection, and comparing before it has would send the old text back.
219
+ afterRenderEffect({ write: () => this.syncWithNative() });
203
220
  }
204
221
  ngOnChanges(changes) {
205
222
  super.ngOnChanges(changes);
@@ -213,7 +230,7 @@ export class TextInput extends ViewBase {
213
230
  onFocus() {
214
231
  const { height, screenY } = this.keyboard.metrics();
215
232
  if (height > 0 && screenY !== undefined)
216
- this.engine.reveal(this.node, { visibleBottom: screenY });
233
+ this.engine.reveal(this.node, { visibleBottom: screenY - this.keyboard.windowTop });
217
234
  }
218
235
  /** Bring the keyboard up on this field. A view command, so it reaches native directly. */
219
236
  focus() {
@@ -248,8 +265,50 @@ export class TextInput extends ViewBase {
248
265
  this.eventCount.set(native?.eventCount ?? this.eventCount() + 1);
249
266
  this.changeText.emit(text);
250
267
  }
268
+ /** Send native the text and selection it does not have, as one `setTextAndSelection`. */
269
+ syncWithNative() {
270
+ const desired = this.value();
271
+ const count = this.eventCount();
272
+ const selection = this.resolvedSelection();
273
+ this.selectionReports();
274
+ const textChanged = this.lastNativeText !== null && this.lastNativeText !== desired;
275
+ if (!textChanged && !this.differsFromNative(selection))
276
+ return;
277
+ if (textChanged)
278
+ this.lastNativeText = desired;
279
+ if (selection)
280
+ this.lastSelection = selection;
281
+ // The `text` prop alone may be ignored as stale, so send it as a command with the count
282
+ // native expects, and the text and bound selection as RN does whichever of them changed.
283
+ this.engine.dispatchCommand(this.node, 'setTextAndSelection', [
284
+ count,
285
+ desired,
286
+ selection?.start ?? -1,
287
+ selection?.end ?? -1,
288
+ ]);
289
+ // Fabric sized the field for the text it had; see `HostEngine.remeasure`.
290
+ if (textChanged)
291
+ this.engine.remeasure(this.node);
292
+ }
293
+ /**
294
+ * Whether a bound selection is not the one native last had. The prop alone does not move the
295
+ * caret: Android has no `selection` prop, and iOS reads it only as the field appears or its
296
+ * text is replaced.
297
+ */
298
+ differsFromNative(selection) {
299
+ return (selection !== undefined &&
300
+ (selection.start !== this.lastSelection.start || selection.end !== this.lastSelection.end));
301
+ }
302
+ /** What native now has selected, which a bound selection that differs is sent over again. */
303
+ onNativeSelectionChange(event) {
304
+ const selection = event.nativeEvent?.selection;
305
+ if (!selection)
306
+ return;
307
+ this.lastSelection = { start: selection.start, end: selection.end };
308
+ this.selectionReports.update((n) => n + 1);
309
+ }
251
310
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: TextInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
252
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: TextInput, isStandalone: true, selector: "text-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", 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 }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, scrollEnabled: { classPropertyName: "scrollEnabled", publicName: "scrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderTextColor: { classPropertyName: "placeholderTextColor", publicName: "placeholderTextColor", isSignal: true, isRequired: false, transformFunction: null }, keyboardType: { classPropertyName: "keyboardType", publicName: "keyboardType", isSignal: true, isRequired: false, transformFunction: null }, returnKeyType: { classPropertyName: "returnKeyType", publicName: "returnKeyType", isSignal: true, isRequired: false, transformFunction: null }, submitBehavior: { classPropertyName: "submitBehavior", publicName: "submitBehavior", isSignal: true, isRequired: false, transformFunction: null }, secureTextEntry: { classPropertyName: "secureTextEntry", publicName: "secureTextEntry", isSignal: true, isRequired: false, transformFunction: null }, autoCapitalize: { classPropertyName: "autoCapitalize", publicName: "autoCapitalize", isSignal: true, isRequired: false, transformFunction: null }, autoCorrect: { classPropertyName: "autoCorrect", publicName: "autoCorrect", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, numberOfLines: { classPropertyName: "numberOfLines", publicName: "numberOfLines", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, selectionColor: { classPropertyName: "selectionColor", publicName: "selectionColor", isSignal: true, isRequired: false, transformFunction: null }, selectTextOnFocus: { classPropertyName: "selectTextOnFocus", publicName: "selectTextOnFocus", isSignal: true, isRequired: false, transformFunction: null }, caretHidden: { classPropertyName: "caretHidden", publicName: "caretHidden", isSignal: true, isRequired: false, transformFunction: null }, contextMenuHidden: { classPropertyName: "contextMenuHidden", publicName: "contextMenuHidden", isSignal: true, isRequired: false, transformFunction: null }, textAlign: { classPropertyName: "textAlign", publicName: "textAlign", isSignal: true, isRequired: false, transformFunction: null }, allowFontScaling: { classPropertyName: "allowFontScaling", publicName: "allowFontScaling", isSignal: true, isRequired: false, transformFunction: null }, maxFontSizeMultiplier: { classPropertyName: "maxFontSizeMultiplier", publicName: "maxFontSizeMultiplier", isSignal: true, isRequired: false, transformFunction: null }, inputAccessoryViewID: { classPropertyName: "inputAccessoryViewID", publicName: "inputAccessoryViewID", isSignal: true, isRequired: false, transformFunction: null }, clearButtonMode: { classPropertyName: "clearButtonMode", publicName: "clearButtonMode", isSignal: true, isRequired: false, transformFunction: null }, clearTextOnFocus: { classPropertyName: "clearTextOnFocus", publicName: "clearTextOnFocus", isSignal: true, isRequired: false, transformFunction: null }, enablesReturnKeyAutomatically: { classPropertyName: "enablesReturnKeyAutomatically", publicName: "enablesReturnKeyAutomatically", isSignal: true, isRequired: false, transformFunction: null }, keyboardAppearance: { classPropertyName: "keyboardAppearance", publicName: "keyboardAppearance", isSignal: true, isRequired: false, transformFunction: null }, passwordRules: { classPropertyName: "passwordRules", publicName: "passwordRules", isSignal: true, isRequired: false, transformFunction: null }, spellCheck: { classPropertyName: "spellCheck", publicName: "spellCheck", isSignal: true, isRequired: false, transformFunction: null }, smartInsertDelete: { classPropertyName: "smartInsertDelete", publicName: "smartInsertDelete", isSignal: true, isRequired: false, transformFunction: null }, dataDetectorTypes: { classPropertyName: "dataDetectorTypes", publicName: "dataDetectorTypes", isSignal: true, isRequired: false, transformFunction: null }, textContentType: { classPropertyName: "textContentType", publicName: "textContentType", isSignal: true, isRequired: false, transformFunction: null }, autoComplete: { classPropertyName: "autoComplete", publicName: "autoComplete", isSignal: true, isRequired: false, transformFunction: null }, underlineColorAndroid: { classPropertyName: "underlineColorAndroid", publicName: "underlineColorAndroid", isSignal: true, isRequired: false, transformFunction: null }, cursorColor: { classPropertyName: "cursorColor", publicName: "cursorColor", isSignal: true, isRequired: false, transformFunction: null }, selectionHandleColor: { classPropertyName: "selectionHandleColor", publicName: "selectionHandleColor", isSignal: true, isRequired: false, transformFunction: null }, textAlignVertical: { classPropertyName: "textAlignVertical", publicName: "textAlignVertical", isSignal: true, isRequired: false, transformFunction: null }, importantForAutofill: { classPropertyName: "importantForAutofill", publicName: "importantForAutofill", isSignal: true, isRequired: false, transformFunction: null }, showSoftInputOnFocus: { classPropertyName: "showSoftInputOnFocus", publicName: "showSoftInputOnFocus", isSignal: true, isRequired: false, transformFunction: null }, disableFullscreenUI: { classPropertyName: "disableFullscreenUI", publicName: "disableFullscreenUI", isSignal: true, isRequired: false, transformFunction: null }, inlineImageLeft: { classPropertyName: "inlineImageLeft", publicName: "inlineImageLeft", isSignal: true, isRequired: false, transformFunction: null }, inlineImagePadding: { classPropertyName: "inlineImagePadding", publicName: "inlineImagePadding", isSignal: true, isRequired: false, transformFunction: null }, textBreakStrategy: { classPropertyName: "textBreakStrategy", publicName: "textBreakStrategy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", changeText: "changeText", touch: "touch" }, host: { listeners: { "blur": "touch.emit()", "focus": "onFocus()", "change": "onNativeChange($event)" }, properties: { "text": "value()", "mostRecentEventCount": "eventCount()", "multiline": "multiline()", "scrollEnabled": "scrollEnabled()", "editable": "resolvedEditable()", "accessibilityState": "accessibilityStateProp()", "attr.data-disabled": "disabled() ? '' : null", "attr.data-invalid": "invalid() ? '' : null", "attr.data-touched": "touched() ? '' : null", "placeholder": "placeholder()", "placeholderTextColor": "placeholderTextColor()", "keyboardType": "keyboardType()", "returnKeyType": "returnKeyType()", "submitBehavior": "resolvedSubmitBehavior()", "secureTextEntry": "secureTextEntry()", "autoCapitalize": "autoCapitalize()", "autoCorrect": "autoCorrect()", "autoFocus": "autoFocus()", "maxLength": "maxLength()", "numberOfLines": "numberOfLines()", "rows": "rows()", "selection": "selection()", "selectionColor": "selectionColor()", "selectTextOnFocus": "selectTextOnFocus()", "caretHidden": "caretHidden()", "contextMenuHidden": "contextMenuHidden()", "textAlign": "textAlign()", "allowFontScaling": "allowFontScaling()", "maxFontSizeMultiplier": "maxFontSizeMultiplier()", "inputAccessoryViewID": "inputAccessoryViewID()", "clearButtonMode": "clearButtonMode()", "clearTextOnFocus": "clearTextOnFocus()", "enablesReturnKeyAutomatically": "enablesReturnKeyAutomatically()", "keyboardAppearance": "keyboardAppearance()", "passwordRules": "passwordRules()", "spellCheck": "spellCheck()", "smartInsertDelete": "smartInsertDelete()", "dataDetectorTypes": "dataDetectorTypes()", "textContentType": "textContentType()", "autoComplete": "autoComplete()", "underlineColorAndroid": "underlineColorAndroid()", "cursorColor": "cursorColor()", "selectionHandleColor": "selectionHandleColor()", "textAlignVertical": "textAlignVertical()", "importantForAutofill": "importantForAutofill()", "showSoftInputOnFocus": "showSoftInputOnFocus()", "disableFullscreenUI": "disableFullscreenUI()", "inlineImageLeft": "inlineImageLeft()", "inlineImagePadding": "inlineImagePadding()", "textBreakStrategy": "textBreakStrategy()" } }, exportAs: ["textInput"], usesInheritance: true, usesOnChanges: true, ngImport: i0 });
311
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: TextInput, isStandalone: true, selector: "text-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", 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 }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, scrollEnabled: { classPropertyName: "scrollEnabled", publicName: "scrollEnabled", isSignal: true, isRequired: false, transformFunction: null }, editable: { classPropertyName: "editable", publicName: "editable", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, placeholderTextColor: { classPropertyName: "placeholderTextColor", publicName: "placeholderTextColor", isSignal: true, isRequired: false, transformFunction: null }, keyboardType: { classPropertyName: "keyboardType", publicName: "keyboardType", isSignal: true, isRequired: false, transformFunction: null }, returnKeyType: { classPropertyName: "returnKeyType", publicName: "returnKeyType", isSignal: true, isRequired: false, transformFunction: null }, submitBehavior: { classPropertyName: "submitBehavior", publicName: "submitBehavior", isSignal: true, isRequired: false, transformFunction: null }, secureTextEntry: { classPropertyName: "secureTextEntry", publicName: "secureTextEntry", isSignal: true, isRequired: false, transformFunction: null }, autoCapitalize: { classPropertyName: "autoCapitalize", publicName: "autoCapitalize", isSignal: true, isRequired: false, transformFunction: null }, autoCorrect: { classPropertyName: "autoCorrect", publicName: "autoCorrect", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, numberOfLines: { classPropertyName: "numberOfLines", publicName: "numberOfLines", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, selectionColor: { classPropertyName: "selectionColor", publicName: "selectionColor", isSignal: true, isRequired: false, transformFunction: null }, selectTextOnFocus: { classPropertyName: "selectTextOnFocus", publicName: "selectTextOnFocus", isSignal: true, isRequired: false, transformFunction: null }, caretHidden: { classPropertyName: "caretHidden", publicName: "caretHidden", isSignal: true, isRequired: false, transformFunction: null }, contextMenuHidden: { classPropertyName: "contextMenuHidden", publicName: "contextMenuHidden", isSignal: true, isRequired: false, transformFunction: null }, textAlign: { classPropertyName: "textAlign", publicName: "textAlign", isSignal: true, isRequired: false, transformFunction: null }, allowFontScaling: { classPropertyName: "allowFontScaling", publicName: "allowFontScaling", isSignal: true, isRequired: false, transformFunction: null }, maxFontSizeMultiplier: { classPropertyName: "maxFontSizeMultiplier", publicName: "maxFontSizeMultiplier", isSignal: true, isRequired: false, transformFunction: null }, inputAccessoryViewID: { classPropertyName: "inputAccessoryViewID", publicName: "inputAccessoryViewID", isSignal: true, isRequired: false, transformFunction: null }, clearButtonMode: { classPropertyName: "clearButtonMode", publicName: "clearButtonMode", isSignal: true, isRequired: false, transformFunction: null }, clearTextOnFocus: { classPropertyName: "clearTextOnFocus", publicName: "clearTextOnFocus", isSignal: true, isRequired: false, transformFunction: null }, enablesReturnKeyAutomatically: { classPropertyName: "enablesReturnKeyAutomatically", publicName: "enablesReturnKeyAutomatically", isSignal: true, isRequired: false, transformFunction: null }, keyboardAppearance: { classPropertyName: "keyboardAppearance", publicName: "keyboardAppearance", isSignal: true, isRequired: false, transformFunction: null }, passwordRules: { classPropertyName: "passwordRules", publicName: "passwordRules", isSignal: true, isRequired: false, transformFunction: null }, spellCheck: { classPropertyName: "spellCheck", publicName: "spellCheck", isSignal: true, isRequired: false, transformFunction: null }, smartInsertDelete: { classPropertyName: "smartInsertDelete", publicName: "smartInsertDelete", isSignal: true, isRequired: false, transformFunction: null }, dataDetectorTypes: { classPropertyName: "dataDetectorTypes", publicName: "dataDetectorTypes", isSignal: true, isRequired: false, transformFunction: null }, textContentType: { classPropertyName: "textContentType", publicName: "textContentType", isSignal: true, isRequired: false, transformFunction: null }, autoComplete: { classPropertyName: "autoComplete", publicName: "autoComplete", isSignal: true, isRequired: false, transformFunction: null }, underlineColorAndroid: { classPropertyName: "underlineColorAndroid", publicName: "underlineColorAndroid", isSignal: true, isRequired: false, transformFunction: null }, cursorColor: { classPropertyName: "cursorColor", publicName: "cursorColor", isSignal: true, isRequired: false, transformFunction: null }, selectionHandleColor: { classPropertyName: "selectionHandleColor", publicName: "selectionHandleColor", isSignal: true, isRequired: false, transformFunction: null }, textAlignVertical: { classPropertyName: "textAlignVertical", publicName: "textAlignVertical", isSignal: true, isRequired: false, transformFunction: null }, importantForAutofill: { classPropertyName: "importantForAutofill", publicName: "importantForAutofill", isSignal: true, isRequired: false, transformFunction: null }, showSoftInputOnFocus: { classPropertyName: "showSoftInputOnFocus", publicName: "showSoftInputOnFocus", isSignal: true, isRequired: false, transformFunction: null }, disableFullscreenUI: { classPropertyName: "disableFullscreenUI", publicName: "disableFullscreenUI", isSignal: true, isRequired: false, transformFunction: null }, inlineImageLeft: { classPropertyName: "inlineImageLeft", publicName: "inlineImageLeft", isSignal: true, isRequired: false, transformFunction: null }, inlineImagePadding: { classPropertyName: "inlineImagePadding", publicName: "inlineImagePadding", isSignal: true, isRequired: false, transformFunction: null }, textBreakStrategy: { classPropertyName: "textBreakStrategy", publicName: "textBreakStrategy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", changeText: "changeText", touch: "touch" }, host: { listeners: { "blur": "touch.emit()", "focus": "onFocus()", "change": "onNativeChange($event)", "selectionChange": "onNativeSelectionChange($event)" }, properties: { "text": "value()", "mostRecentEventCount": "eventCount()", "multiline": "multiline()", "scrollEnabled": "scrollEnabled()", "editable": "resolvedEditable()", "accessibilityState": "accessibilityStateProp()", "attr.data-disabled": "disabled() ? '' : null", "attr.data-invalid": "invalid() ? '' : null", "attr.data-touched": "touched() ? '' : null", "placeholder": "placeholder()", "placeholderTextColor": "placeholderTextColor()", "keyboardType": "keyboardType()", "returnKeyType": "returnKeyType()", "submitBehavior": "resolvedSubmitBehavior()", "secureTextEntry": "secureTextEntry()", "autoCapitalize": "autoCapitalize()", "autoCorrect": "autoCorrect()", "autoFocus": "autoFocus()", "maxLength": "maxLength()", "numberOfLines": "numberOfLines()", "rows": "rows()", "selection": "resolvedSelection()", "selectionColor": "selectionColor()", "selectTextOnFocus": "selectTextOnFocus()", "caretHidden": "caretHidden()", "contextMenuHidden": "contextMenuHidden()", "textAlign": "textAlign()", "allowFontScaling": "allowFontScaling()", "maxFontSizeMultiplier": "maxFontSizeMultiplier()", "inputAccessoryViewID": "inputAccessoryViewID()", "clearButtonMode": "clearButtonMode()", "clearTextOnFocus": "clearTextOnFocus()", "enablesReturnKeyAutomatically": "enablesReturnKeyAutomatically()", "keyboardAppearance": "keyboardAppearance()", "passwordRules": "passwordRules()", "spellCheck": "spellCheck()", "smartInsertDelete": "smartInsertDelete()", "dataDetectorTypes": "dataDetectorTypes()", "textContentType": "textContentType()", "autoComplete": "autoComplete()", "underlineColorAndroid": "underlineColorAndroid()", "cursorColor": "resolvedCursorColor()", "selectionHandleColor": "resolvedSelectionHandleColor()", "textAlignVertical": "textAlignVertical()", "importantForAutofill": "importantForAutofill()", "showSoftInputOnFocus": "showSoftInputOnFocus()", "disableFullscreenUI": "disableFullscreenUI()", "inlineImageLeft": "inlineImageLeft()", "inlineImagePadding": "inlineImagePadding()", "textBreakStrategy": "textBreakStrategy()" } }, exportAs: ["textInput"], usesInheritance: true, usesOnChanges: true, ngImport: i0 });
253
312
  }
254
313
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: TextInput, decorators: [{
255
314
  type: Directive,
@@ -282,7 +341,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
282
341
  '[maxLength]': 'maxLength()',
283
342
  '[numberOfLines]': 'numberOfLines()',
284
343
  '[rows]': 'rows()',
285
- '[selection]': 'selection()',
344
+ '[selection]': 'resolvedSelection()',
286
345
  '[selectionColor]': 'selectionColor()',
287
346
  '[selectTextOnFocus]': 'selectTextOnFocus()',
288
347
  '[caretHidden]': 'caretHidden()',
@@ -302,8 +361,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
302
361
  '[textContentType]': 'textContentType()',
303
362
  '[autoComplete]': 'autoComplete()',
304
363
  '[underlineColorAndroid]': 'underlineColorAndroid()',
305
- '[cursorColor]': 'cursorColor()',
306
- '[selectionHandleColor]': 'selectionHandleColor()',
364
+ '[cursorColor]': 'resolvedCursorColor()',
365
+ '[selectionHandleColor]': 'resolvedSelectionHandleColor()',
307
366
  '[textAlignVertical]': 'textAlignVertical()',
308
367
  '[importantForAutofill]': 'importantForAutofill()',
309
368
  '[showSoftInputOnFocus]': 'showSoftInputOnFocus()',
@@ -312,6 +371,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
312
371
  '[inlineImagePadding]': 'inlineImagePadding()',
313
372
  '[textBreakStrategy]': 'textBreakStrategy()',
314
373
  '(change)': 'onNativeChange($event)',
374
+ '(selectionChange)': 'onNativeSelectionChange($event)',
315
375
  },
316
376
  }]
317
377
  }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], changeText: [{ type: i0.Output, args: ["changeText"] }], touch: [{ type: i0.Output, args: ["touch"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], scrollEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "scrollEnabled", required: false }] }], editable: [{ type: i0.Input, args: [{ isSignal: true, alias: "editable", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], placeholderTextColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholderTextColor", required: false }] }], keyboardType: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardType", required: false }] }], returnKeyType: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnKeyType", required: false }] }], submitBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitBehavior", required: false }] }], secureTextEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "secureTextEntry", required: false }] }], autoCapitalize: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCapitalize", required: false }] }], autoCorrect: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoCorrect", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], numberOfLines: [{ type: i0.Input, args: [{ isSignal: true, alias: "numberOfLines", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], selectionColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionColor", required: false }] }], selectTextOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectTextOnFocus", required: false }] }], caretHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "caretHidden", required: false }] }], contextMenuHidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "contextMenuHidden", required: false }] }], textAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "textAlign", required: false }] }], allowFontScaling: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowFontScaling", required: false }] }], maxFontSizeMultiplier: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFontSizeMultiplier", required: false }] }], inputAccessoryViewID: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputAccessoryViewID", required: false }] }], clearButtonMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearButtonMode", required: false }] }], clearTextOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearTextOnFocus", required: false }] }], enablesReturnKeyAutomatically: [{ type: i0.Input, args: [{ isSignal: true, alias: "enablesReturnKeyAutomatically", required: false }] }], keyboardAppearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardAppearance", required: false }] }], passwordRules: [{ type: i0.Input, args: [{ isSignal: true, alias: "passwordRules", required: false }] }], spellCheck: [{ type: i0.Input, args: [{ isSignal: true, alias: "spellCheck", required: false }] }], smartInsertDelete: [{ type: i0.Input, args: [{ isSignal: true, alias: "smartInsertDelete", required: false }] }], dataDetectorTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataDetectorTypes", required: false }] }], textContentType: [{ type: i0.Input, args: [{ isSignal: true, alias: "textContentType", required: false }] }], autoComplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoComplete", required: false }] }], underlineColorAndroid: [{ type: i0.Input, args: [{ isSignal: true, alias: "underlineColorAndroid", required: false }] }], cursorColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "cursorColor", required: false }] }], selectionHandleColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionHandleColor", required: false }] }], textAlignVertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "textAlignVertical", required: false }] }], importantForAutofill: [{ type: i0.Input, args: [{ isSignal: true, alias: "importantForAutofill", required: false }] }], showSoftInputOnFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSoftInputOnFocus", required: false }] }], disableFullscreenUI: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableFullscreenUI", required: false }] }], inlineImageLeft: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlineImageLeft", required: false }] }], inlineImagePadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlineImagePadding", required: false }] }], textBreakStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "textBreakStrategy", required: false }] }] } });
@@ -1 +1 @@
1
- {"version":3,"file":"text-input.js","sourceRoot":"","sources":["../src/text-input.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n type SimpleChanges,\n afterRenderEffect,\n booleanAttribute,\n computed,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport { Keyboard } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport { ControlledModel } from './controlled-model.ts';\nimport type { TextInputChangePayload } from './events.ts';\nimport { optionalBoolean, optionalNumber } from './transforms.ts';\nimport { ViewBase } from './view-base.ts';\n\nexport type KeyboardType =\n | 'default'\n | 'number-pad'\n | 'decimal-pad'\n | 'numeric'\n | 'email-address'\n | 'phone-pad'\n | 'url'\n | 'ascii-capable'\n | 'numbers-and-punctuation'\n | 'name-phone-pad'\n | 'twitter'\n | 'web-search'\n | 'visible-password';\nexport type ReturnKeyType =\n | 'done'\n | 'go'\n | 'next'\n | 'search'\n | 'send'\n | 'none'\n | 'previous'\n | 'default'\n | 'emergency-call'\n | 'google'\n | 'join'\n | 'route'\n | 'yahoo';\nexport type SubmitBehavior = 'submit' | 'blurAndSubmit' | 'newline';\n\n/**\n * A text field, wired for signal forms. Commits as `TextInput`, single or multi line by prop.\n *\n * `value` is a `model()`, which is all `FormValueControl<string>` requires, so binding it into a\n * form is `<text-input [formField]=\"f.name\" />` with no adapter class. The rest of the contract is\n * declared too, because Signal Forms only writes to inputs of those names: `disabled` and\n * `readonly` turn `editable` off, `invalid` and `touched` come out as `data-invalid` and\n * `data-touched` for a stylesheet, and `touch` fires from the native blur - there is no DOM one\n * for the form to hear instead. `(changeText)` carries\n * the new string alone, as RN's `onChangeText` does; the raw `(change)`, `(focus)`, `(blur)`,\n * `(submitEditing)`, `(endEditing)`, `(selectionChange)`, `(keyPress)`, `(contentSizeChange)`\n * and `(scroll)` are element events typed in `events.ts`.\n *\n * The interesting part is the echo. A native text field owns state the user is actively\n * changing, so JS and native disagree constantly. RN's protocol: native reports an `eventCount`\n * with every change, JS passes the most recent one back as a prop, and native ignores a `text`\n * update whose count is stale, meaning the user has typed since. Without it, a controlled input\n * fights the cursor and drops characters under fast typing.\n *\n * The other direction is native holding text the app does not: a value set from code after the\n * user has typed, or a keystroke the app refused or rewrote. It is controlled as RN's field is -\n * bind `value` and the app's value is what the field shows - so once each pass has rendered, the\n * field compares the model with the text native last reported and sends the difference as the\n * `setTextAndSelection` command. See `ControlledModel` for how a refusal reaches the model.\n * A value set that way is also measured again, since Fabric sizes the field from native state\n * that takes the new text only after measuring it: a composer cleared after sending shrinks.\n */\n@Directive({\n selector: 'text-input',\n exportAs: 'textInput',\n host: {\n '[text]': 'value()',\n '[mostRecentEventCount]': 'eventCount()',\n '[multiline]': 'multiline()',\n '[scrollEnabled]': 'scrollEnabled()',\n '[editable]': 'resolvedEditable()',\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 '[attr.data-disabled]': \"disabled() ? '' : null\",\n '[attr.data-invalid]': \"invalid() ? '' : null\",\n '[attr.data-touched]': \"touched() ? '' : null\",\n '(blur)': 'touch.emit()',\n '(focus)': 'onFocus()',\n '[placeholder]': 'placeholder()',\n '[placeholderTextColor]': 'placeholderTextColor()',\n '[keyboardType]': 'keyboardType()',\n '[returnKeyType]': 'returnKeyType()',\n '[submitBehavior]': 'resolvedSubmitBehavior()',\n '[secureTextEntry]': 'secureTextEntry()',\n '[autoCapitalize]': 'autoCapitalize()',\n '[autoCorrect]': 'autoCorrect()',\n '[autoFocus]': 'autoFocus()',\n '[maxLength]': 'maxLength()',\n '[numberOfLines]': 'numberOfLines()',\n '[rows]': 'rows()',\n '[selection]': 'selection()',\n '[selectionColor]': 'selectionColor()',\n '[selectTextOnFocus]': 'selectTextOnFocus()',\n '[caretHidden]': 'caretHidden()',\n '[contextMenuHidden]': 'contextMenuHidden()',\n '[textAlign]': 'textAlign()',\n '[allowFontScaling]': 'allowFontScaling()',\n '[maxFontSizeMultiplier]': 'maxFontSizeMultiplier()',\n '[inputAccessoryViewID]': 'inputAccessoryViewID()',\n '[clearButtonMode]': 'clearButtonMode()',\n '[clearTextOnFocus]': 'clearTextOnFocus()',\n '[enablesReturnKeyAutomatically]': 'enablesReturnKeyAutomatically()',\n '[keyboardAppearance]': 'keyboardAppearance()',\n '[passwordRules]': 'passwordRules()',\n '[spellCheck]': 'spellCheck()',\n '[smartInsertDelete]': 'smartInsertDelete()',\n '[dataDetectorTypes]': 'dataDetectorTypes()',\n '[textContentType]': 'textContentType()',\n '[autoComplete]': 'autoComplete()',\n '[underlineColorAndroid]': 'underlineColorAndroid()',\n '[cursorColor]': 'cursorColor()',\n '[selectionHandleColor]': 'selectionHandleColor()',\n '[textAlignVertical]': 'textAlignVertical()',\n '[importantForAutofill]': 'importantForAutofill()',\n '[showSoftInputOnFocus]': 'showSoftInputOnFocus()',\n '[disableFullscreenUI]': 'disableFullscreenUI()',\n '[inlineImageLeft]': 'inlineImageLeft()',\n '[inlineImagePadding]': 'inlineImagePadding()',\n '[textBreakStrategy]': 'textBreakStrategy()',\n '(change)': 'onNativeChange($event)',\n },\n})\nexport class TextInput extends ViewBase {\n readonly value = model<string>('');\n /** The new text alone, on every native change. */\n readonly changeText = output<string>();\n /** Focus left the field. What Signal Forms marks a field touched from. */\n readonly touch = output<void>();\n\n /** Refuse input and announce as disabled. Wins over `editable`. */\n readonly disabled = input(undefined, { transform: optionalBoolean });\n /** Refuse input, though still announced as enabled. Wins over `editable`. */\n readonly readonly = 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\n /** Allow more than one line. */\n readonly multiline = input(undefined, { transform: optionalBoolean });\n /** iOS: whether a multiline field scrolls its text; off, it grows with it instead. */\n readonly scrollEnabled = input(undefined, { transform: optionalBoolean });\n /** Set to false to make the field read-only. */\n readonly editable = input(undefined, { transform: optionalBoolean });\n /** Shown while the field is empty. */\n readonly placeholder = input<string>();\n readonly placeholderTextColor = input<string>();\n /** Which keyboard to show. */\n readonly keyboardType = input<KeyboardType>();\n /** The label on the return key. */\n readonly returnKeyType = input<ReturnKeyType>();\n /** What return does. Defaults to `blurAndSubmit` on one line and `newline` on many. */\n readonly submitBehavior = input<SubmitBehavior>();\n /** Hide what is typed, for passwords. */\n readonly secureTextEntry = input(undefined, { transform: optionalBoolean });\n /** When the keyboard capitalises. Defaults to `sentences`. */\n readonly autoCapitalize = input<'none' | 'sentences' | 'words' | 'characters'>();\n /** Auto-correct while typing. Defaults to true. */\n readonly autoCorrect = input(undefined, { transform: optionalBoolean });\n /** Focus the field as soon as it mounts. */\n readonly autoFocus = input(undefined, { transform: optionalBoolean });\n /** The most characters allowed, enforced natively so nothing flickers. */\n readonly maxLength = input(undefined, { transform: optionalNumber });\n /** Android: the height of a multiline field, in lines. */\n readonly numberOfLines = input(undefined, { transform: optionalNumber });\n /**\n * How many lines of its `line-height` a multiline field is tall, where nothing sets its height,\n * as a `<textarea rows>` is. Read by the engine, which sizes the field; never sent to native.\n */\n readonly rows = input(undefined, { transform: optionalNumber });\n /** The selected range, or the caret when `start` equals `end`. */\n readonly selection = input<{ readonly start: number; readonly end?: number }>();\n readonly selectionColor = input<string>();\n /** Select everything on focus. */\n readonly selectTextOnFocus = input(undefined, { transform: optionalBoolean });\n readonly caretHidden = input(undefined, { transform: optionalBoolean });\n /** Disable the copy, paste and share menu. */\n readonly contextMenuHidden = input(undefined, { transform: optionalBoolean });\n readonly textAlign = input<'left' | 'center' | 'right'>();\n readonly allowFontScaling = input(undefined, { transform: optionalBoolean });\n readonly maxFontSizeMultiplier = input(undefined, { transform: optionalNumber });\n /** iOS: the `nativeID` of an `<input-accessory-view>` to dock above the keyboard. */\n readonly inputAccessoryViewID = input<string>();\n /** iOS: when the clear button shows. */\n readonly clearButtonMode = input<'never' | 'while-editing' | 'unless-editing' | 'always'>();\n /** iOS: empty the field on focus. */\n readonly clearTextOnFocus = input(undefined, { transform: optionalBoolean });\n /** iOS: disable the return key while the field is empty. */\n readonly enablesReturnKeyAutomatically = input(undefined, { transform: optionalBoolean });\n /** iOS: the keyboard's colour scheme. */\n readonly keyboardAppearance = input<'default' | 'light' | 'dark'>();\n /** iOS: rules for a generated password, in Apple's Password Rules syntax. */\n readonly passwordRules = input<string>();\n /** iOS: underline misspellings. Follows `autoCorrect` by default. */\n readonly spellCheck = input(undefined, { transform: optionalBoolean });\n /** iOS: add and remove spaces around pasted and deleted words. */\n readonly smartInsertDelete = input(undefined, { transform: optionalBoolean });\n /** iOS, multiline: turn phone numbers, links and addresses into taps. */\n readonly dataDetectorTypes = input<string | readonly string[]>();\n /** iOS: what the field holds, so the system can autofill it. */\n readonly textContentType = input<string>();\n /** Android: the autofill hint. */\n readonly autoComplete = input<string>();\n /**\n * Android: the underline colour, transparent by default.\n *\n * Not a taste call. Android's `EditText` carries the platform's own underline in its background\n * drawable, so a field with a border of its own draws two: the app's box, and a grey line\n * through it that turns blue on focus. React Native's `TextInput` defaults this prop to\n * `'transparent'` for exactly that reason, and this is that default rather than a new opinion.\n * Ignored on iOS, where the native field has no such chrome.\n */\n readonly underlineColorAndroid = input<string>('transparent');\n /** Android: the caret colour. */\n readonly cursorColor = input<string>();\n /** Android: the selection handle colour. */\n readonly selectionHandleColor = input<string>();\n /** Android: where text sits in a tall field. */\n readonly textAlignVertical = input<'auto' | 'top' | 'bottom' | 'center'>();\n /** Android: whether autofill may fill this field. */\n readonly importantForAutofill = input<\n 'auto' | 'no' | 'noExcludeDescendants' | 'yes' | 'yesExcludeDescendants'\n >();\n /** Android: set to false to focus without raising the keyboard. */\n readonly showSoftInputOnFocus = input(undefined, { transform: optionalBoolean });\n /** Android: keep the field in place in landscape rather than going full screen. */\n readonly disableFullscreenUI = input(undefined, { transform: optionalBoolean });\n /** Android: a drawable resource shown at the left of the field. */\n readonly inlineImageLeft = input<string>();\n readonly inlineImagePadding = input(undefined, { transform: optionalNumber });\n /** Android: the line-breaking strategy. */\n readonly textBreakStrategy = input<'simple' | 'highQuality' | 'balanced'>();\n\n protected readonly eventCount = signal(0);\n\n protected readonly resolvedEditable = computed(() =>\n this.disabled() || this.readonly() ? false : this.editable(),\n );\n\n protected override disabledForAccessibility(): boolean | undefined {\n return this.disabled();\n }\n\n protected readonly resolvedSubmitBehavior = computed(() => {\n const explicit = this.submitBehavior();\n // A single-line field has no line to add, so `newline` collapses to the default.\n if (explicit && (this.multiline() || explicit !== 'newline')) return explicit;\n return this.multiline() ? 'newline' : 'blurAndSubmit';\n });\n\n private readonly keyboard = inject(Keyboard);\n /** What native last reported, so a value set from code can be told from an echo. */\n private lastNativeText: string | null = null;\n private readonly control = new ControlledModel(this.value, 'value');\n\n constructor() {\n super();\n // RN's layout effect, keyed as RN keys it: the value, and the count every native change\n // moves on, so a refusal that leaves the value where it was is compared all the same. After\n // render rather than as an ordinary effect, because an accepted edit only reaches the model\n // during change detection, and comparing before it has would send the old text back.\n afterRenderEffect({\n write: () => {\n const desired = this.value();\n const count = this.eventCount();\n if (this.lastNativeText === null || this.lastNativeText === desired) return;\n // The `text` prop alone may be ignored as stale, so send it as a command with the count\n // native expects.\n this.lastNativeText = desired;\n this.engine.dispatchCommand(this.node, 'setTextAndSelection', [count, desired, -1, -1]);\n // Fabric sized the field for the text it had; see `HostEngine.remeasure`.\n this.engine.remeasure(this.node);\n },\n });\n }\n\n override ngOnChanges(changes: SimpleChanges): void {\n super.ngOnChanges(changes);\n this.control.noteChanges(changes);\n }\n\n /**\n * With the keyboard already up, keep the field clear of it. UIKit scrolls a field clear as the\n * keyboard rises, not when focus moves from one field to another beneath it, which is what a\n * form's Next key does; native form screens keep the new field on screen either way.\n */\n protected onFocus(): void {\n const { height, screenY } = this.keyboard.metrics();\n if (height > 0 && screenY !== undefined)\n this.engine.reveal(this.node, { visibleBottom: screenY });\n }\n\n /** Bring the keyboard up on this field. A view command, so it reaches native directly. */\n focus(): void {\n this.engine.dispatchCommand(this.node, 'focus');\n }\n\n blur(): void {\n this.engine.dispatchCommand(this.node, 'blur');\n }\n\n /** Empty the field. */\n clear(): void {\n this.value.set('');\n }\n\n /** Move the caret, or select a range. */\n setSelection(start: number, end = start): void {\n this.engine.dispatchCommand(this.node, 'setTextAndSelection', [\n this.eventCount(),\n null,\n start,\n end,\n ]);\n }\n\n /** Whether this field currently has the keyboard. */\n isFocused(): boolean {\n return this.engine.focused === this.node;\n }\n\n protected onNativeChange(event: NativeSyntheticEvent<Partial<TextInputChangePayload>>): void {\n const native = event.nativeEvent;\n const text = native?.text ?? '';\n // Order matters, as it does in RN: the value first, the count last.\n this.lastNativeText = text;\n this.control.propose(text);\n this.eventCount.set(native?.eventCount ?? this.eventCount() + 1);\n this.changeText.emit(text);\n }\n}\n"]}
1
+ {"version":3,"file":"text-input.js","sourceRoot":"","sources":["../src/text-input.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Directive,\n type SimpleChanges,\n afterRenderEffect,\n booleanAttribute,\n computed,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport { Keyboard } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport { ControlledModel } from './controlled-model.ts';\nimport type { SelectionChangePayload, TextInputChangePayload } from './events.ts';\nimport { optionalBoolean, optionalNumber } from './transforms.ts';\nimport { ViewBase } from './view-base.ts';\n\nexport type KeyboardType =\n | 'default'\n | 'number-pad'\n | 'decimal-pad'\n | 'numeric'\n | 'email-address'\n | 'phone-pad'\n | 'url'\n | 'ascii-capable'\n | 'numbers-and-punctuation'\n | 'name-phone-pad'\n | 'twitter'\n | 'web-search'\n | 'visible-password';\nexport type ReturnKeyType =\n | 'done'\n | 'go'\n | 'next'\n | 'search'\n | 'send'\n | 'none'\n | 'previous'\n | 'default'\n | 'emergency-call'\n | 'google'\n | 'join'\n | 'route'\n | 'yahoo';\nexport type SubmitBehavior = 'submit' | 'blurAndSubmit' | 'newline';\n\n/**\n * A text field, wired for signal forms. Commits as `TextInput`, single or multi line by prop.\n *\n * `value` is a `model()`, which is all `FormValueControl<string>` requires, so binding it into a\n * form is `<text-input [formField]=\"f.name\" />` with no adapter class. The rest of the contract is\n * declared too, because Signal Forms only writes to inputs of those names: `disabled` and\n * `readonly` turn `editable` off, `invalid` and `touched` come out as `data-invalid` and\n * `data-touched` for a stylesheet, and `touch` fires from the native blur - there is no DOM one\n * for the form to hear instead. `(changeText)` carries\n * the new string alone, as RN's `onChangeText` does; the raw `(change)`, `(focus)`, `(blur)`,\n * `(submitEditing)`, `(endEditing)`, `(selectionChange)`, `(keyPress)`, `(contentSizeChange)`\n * and `(scroll)` are element events typed in `events.ts`.\n *\n * The interesting part is the echo. A native text field owns state the user is actively\n * changing, so JS and native disagree constantly. RN's protocol: native reports an `eventCount`\n * with every change, JS passes the most recent one back as a prop, and native ignores a `text`\n * update whose count is stale, meaning the user has typed since. Without it, a controlled input\n * fights the cursor and drops characters under fast typing.\n *\n * The other direction is native holding text the app does not: a value set from code after the\n * user has typed, or a keystroke the app refused or rewrote. It is controlled as RN's field is -\n * bind `value` and the app's value is what the field shows - so once each pass has rendered, the\n * field compares the model with the text native last reported and sends the difference as the\n * `setTextAndSelection` command. See `ControlledModel` for how a refusal reaches the model.\n * A value set that way is also measured again, since Fabric sizes the field from native state\n * that takes the new text only after measuring it: a composer cleared after sending shrinks.\n */\n@Directive({\n selector: 'text-input',\n exportAs: 'textInput',\n host: {\n '[text]': 'value()',\n '[mostRecentEventCount]': 'eventCount()',\n '[multiline]': 'multiline()',\n '[scrollEnabled]': 'scrollEnabled()',\n '[editable]': 'resolvedEditable()',\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 '[attr.data-disabled]': \"disabled() ? '' : null\",\n '[attr.data-invalid]': \"invalid() ? '' : null\",\n '[attr.data-touched]': \"touched() ? '' : null\",\n '(blur)': 'touch.emit()',\n '(focus)': 'onFocus()',\n '[placeholder]': 'placeholder()',\n '[placeholderTextColor]': 'placeholderTextColor()',\n '[keyboardType]': 'keyboardType()',\n '[returnKeyType]': 'returnKeyType()',\n '[submitBehavior]': 'resolvedSubmitBehavior()',\n '[secureTextEntry]': 'secureTextEntry()',\n '[autoCapitalize]': 'autoCapitalize()',\n '[autoCorrect]': 'autoCorrect()',\n '[autoFocus]': 'autoFocus()',\n '[maxLength]': 'maxLength()',\n '[numberOfLines]': 'numberOfLines()',\n '[rows]': 'rows()',\n '[selection]': 'resolvedSelection()',\n '[selectionColor]': 'selectionColor()',\n '[selectTextOnFocus]': 'selectTextOnFocus()',\n '[caretHidden]': 'caretHidden()',\n '[contextMenuHidden]': 'contextMenuHidden()',\n '[textAlign]': 'textAlign()',\n '[allowFontScaling]': 'allowFontScaling()',\n '[maxFontSizeMultiplier]': 'maxFontSizeMultiplier()',\n '[inputAccessoryViewID]': 'inputAccessoryViewID()',\n '[clearButtonMode]': 'clearButtonMode()',\n '[clearTextOnFocus]': 'clearTextOnFocus()',\n '[enablesReturnKeyAutomatically]': 'enablesReturnKeyAutomatically()',\n '[keyboardAppearance]': 'keyboardAppearance()',\n '[passwordRules]': 'passwordRules()',\n '[spellCheck]': 'spellCheck()',\n '[smartInsertDelete]': 'smartInsertDelete()',\n '[dataDetectorTypes]': 'dataDetectorTypes()',\n '[textContentType]': 'textContentType()',\n '[autoComplete]': 'autoComplete()',\n '[underlineColorAndroid]': 'underlineColorAndroid()',\n '[cursorColor]': 'resolvedCursorColor()',\n '[selectionHandleColor]': 'resolvedSelectionHandleColor()',\n '[textAlignVertical]': 'textAlignVertical()',\n '[importantForAutofill]': 'importantForAutofill()',\n '[showSoftInputOnFocus]': 'showSoftInputOnFocus()',\n '[disableFullscreenUI]': 'disableFullscreenUI()',\n '[inlineImageLeft]': 'inlineImageLeft()',\n '[inlineImagePadding]': 'inlineImagePadding()',\n '[textBreakStrategy]': 'textBreakStrategy()',\n '(change)': 'onNativeChange($event)',\n '(selectionChange)': 'onNativeSelectionChange($event)',\n },\n})\nexport class TextInput extends ViewBase {\n readonly value = model<string>('');\n /** The new text alone, on every native change. */\n readonly changeText = output<string>();\n /** Focus left the field. What Signal Forms marks a field touched from. */\n readonly touch = output<void>();\n\n /** Refuse input and announce as disabled. Wins over `editable`. */\n readonly disabled = input(undefined, { transform: optionalBoolean });\n /** Refuse input, though still announced as enabled. Wins over `editable`. */\n readonly readonly = 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\n /** Allow more than one line. */\n readonly multiline = input(undefined, { transform: optionalBoolean });\n /** iOS: whether a multiline field scrolls its text; off, it grows with it instead. */\n readonly scrollEnabled = input(undefined, { transform: optionalBoolean });\n /** Set to false to make the field read-only. */\n readonly editable = input(undefined, { transform: optionalBoolean });\n /** Shown while the field is empty. */\n readonly placeholder = input<string>();\n readonly placeholderTextColor = input<string>();\n /** Which keyboard to show. */\n readonly keyboardType = input<KeyboardType>();\n /** The label on the return key. */\n readonly returnKeyType = input<ReturnKeyType>();\n /** What return does. Defaults to `blurAndSubmit` on one line and `newline` on many. */\n readonly submitBehavior = input<SubmitBehavior>();\n /** Hide what is typed, for passwords. */\n readonly secureTextEntry = input(undefined, { transform: optionalBoolean });\n /** When the keyboard capitalises. Defaults to `sentences`. */\n readonly autoCapitalize = input<'none' | 'sentences' | 'words' | 'characters'>();\n /** Auto-correct while typing. Defaults to true. */\n readonly autoCorrect = input(undefined, { transform: optionalBoolean });\n /** Focus the field as soon as it mounts. */\n readonly autoFocus = input(undefined, { transform: optionalBoolean });\n /** The most characters allowed, enforced natively so nothing flickers. */\n readonly maxLength = input(undefined, { transform: optionalNumber });\n /** Android: the height of a multiline field, in lines. */\n readonly numberOfLines = input(undefined, { transform: optionalNumber });\n /**\n * How many lines of its `line-height` a multiline field is tall, where nothing sets its height,\n * as a `<textarea rows>` is. Read by the engine, which sizes the field; never sent to native.\n */\n readonly rows = input(undefined, { transform: optionalNumber });\n /**\n * The selected range, or the caret when `end` is left out or equals `start`. Sent to native\n * whenever it differs from what native last had, so a caret the user moves goes back to it.\n */\n readonly selection = input<{ readonly start: number; readonly end?: number }>();\n readonly selectionColor = input<string>();\n /** Select everything on focus. */\n readonly selectTextOnFocus = input(undefined, { transform: optionalBoolean });\n readonly caretHidden = input(undefined, { transform: optionalBoolean });\n /** Disable the copy, paste and share menu. */\n readonly contextMenuHidden = input(undefined, { transform: optionalBoolean });\n readonly textAlign = input<'left' | 'center' | 'right'>();\n readonly allowFontScaling = input(undefined, { transform: optionalBoolean });\n readonly maxFontSizeMultiplier = input(undefined, { transform: optionalNumber });\n /** iOS: the `nativeID` of an `<input-accessory-view>` to dock above the keyboard. */\n readonly inputAccessoryViewID = input<string>();\n /** iOS: when the clear button shows. */\n readonly clearButtonMode = input<'never' | 'while-editing' | 'unless-editing' | 'always'>();\n /** iOS: empty the field on focus. */\n readonly clearTextOnFocus = input(undefined, { transform: optionalBoolean });\n /** iOS: disable the return key while the field is empty. */\n readonly enablesReturnKeyAutomatically = input(undefined, { transform: optionalBoolean });\n /** iOS: the keyboard's colour scheme. */\n readonly keyboardAppearance = input<'default' | 'light' | 'dark'>();\n /** iOS: rules for a generated password, in Apple's Password Rules syntax. */\n readonly passwordRules = input<string>();\n /** iOS: underline misspellings. Follows `autoCorrect` by default. */\n readonly spellCheck = input(undefined, { transform: optionalBoolean });\n /** iOS: add and remove spaces around pasted and deleted words. */\n readonly smartInsertDelete = input(undefined, { transform: optionalBoolean });\n /** iOS, multiline: turn phone numbers, links and addresses into taps. */\n readonly dataDetectorTypes = input<string | readonly string[]>();\n /** iOS: what the field holds, so the system can autofill it. */\n readonly textContentType = input<string>();\n /** Android: the autofill hint. */\n readonly autoComplete = input<string>();\n /**\n * Android: the underline colour, transparent by default.\n *\n * Not a taste call. Android's `EditText` carries the platform's own underline in its background\n * drawable, so a field with a border of its own draws two: the app's box, and a grey line\n * through it that turns blue on focus. React Native's `TextInput` defaults this prop to\n * `'transparent'` for exactly that reason, and this is that default rather than a new opinion.\n * Ignored on iOS, where the native field has no such chrome.\n */\n readonly underlineColorAndroid = input<string>('transparent');\n /** Android: the caret colour. Follows `selectionColor` by default, as RN's does. */\n readonly cursorColor = input<string>();\n /** Android: the selection handle colour. Follows `selectionColor` by default, as RN's does. */\n readonly selectionHandleColor = input<string>();\n /** Android: where text sits in a tall field. */\n readonly textAlignVertical = input<'auto' | 'top' | 'bottom' | 'center'>();\n /** Android: whether autofill may fill this field. */\n readonly importantForAutofill = input<\n 'auto' | 'no' | 'noExcludeDescendants' | 'yes' | 'yesExcludeDescendants'\n >();\n /** Android: set to false to focus without raising the keyboard. */\n readonly showSoftInputOnFocus = input(undefined, { transform: optionalBoolean });\n /** Android: keep the field in place in landscape rather than going full screen. */\n readonly disableFullscreenUI = input(undefined, { transform: optionalBoolean });\n /** Android: a drawable resource shown at the left of the field. */\n readonly inlineImageLeft = input<string>();\n readonly inlineImagePadding = input(undefined, { transform: optionalNumber });\n /** Android: the line-breaking strategy. */\n readonly textBreakStrategy = input<'simple' | 'highQuality' | 'balanced'>();\n\n protected readonly eventCount = signal(0);\n\n /** RN's normalised selection: an `end` left out is `start`, where iOS would read 0. */\n protected readonly resolvedSelection = computed(() => {\n const selection = this.selection();\n return selection\n ? { start: selection.start, end: selection.end ?? selection.start }\n : undefined;\n });\n\n // RN's Android defaults, checked against `undefined` as RN does rather than with `??`.\n protected readonly resolvedCursorColor = computed(() => {\n const color = this.cursorColor();\n return color === undefined ? this.selectionColor() : color;\n });\n protected readonly resolvedSelectionHandleColor = computed(() => {\n const color = this.selectionHandleColor();\n return color === undefined ? this.selectionColor() : color;\n });\n\n protected readonly resolvedEditable = computed(() =>\n this.disabled() || this.readonly() ? false : this.editable(),\n );\n\n protected override disabledForAccessibility(): boolean | undefined {\n return this.disabled();\n }\n\n protected readonly resolvedSubmitBehavior = computed(() => {\n const explicit = this.submitBehavior();\n // A single-line field has no line to add, so `newline` collapses to the default.\n if (explicit && (this.multiline() || explicit !== 'newline')) return explicit;\n return this.multiline() ? 'newline' : 'blurAndSubmit';\n });\n\n private readonly keyboard = inject(Keyboard);\n /** What native last reported, so a value set from code can be told from an echo. */\n private lastNativeText: string | null = null;\n /**\n * The selection native last had, sent or reported, so a bound one is sent only when it differs.\n * Unset to start with, so the first bound selection goes out too.\n */\n private lastSelection = { start: -1, end: -1 };\n /** Moved on every reported selection, so the bound one is compared with it again. */\n private readonly selectionReports = signal(0);\n private readonly control = new ControlledModel(this.value, 'value');\n\n constructor() {\n super();\n // RN's layout effect, keyed as RN keys it: the value, the selection, and the count every\n // native change moves on, so a refusal that leaves the value where it was is compared all the\n // same. After render rather than as an ordinary effect, because an accepted edit only reaches\n // the model during change detection, and comparing before it has would send the old text back.\n afterRenderEffect({ write: () => this.syncWithNative() });\n }\n\n override ngOnChanges(changes: SimpleChanges): void {\n super.ngOnChanges(changes);\n this.control.noteChanges(changes);\n }\n\n /**\n * With the keyboard already up, keep the field clear of it. UIKit scrolls a field clear as the\n * keyboard rises, not when focus moves from one field to another beneath it, which is what a\n * form's Next key does; native form screens keep the new field on screen either way.\n */\n protected onFocus(): void {\n const { height, screenY } = this.keyboard.metrics();\n if (height > 0 && screenY !== undefined)\n this.engine.reveal(this.node, { visibleBottom: screenY - this.keyboard.windowTop });\n }\n\n /** Bring the keyboard up on this field. A view command, so it reaches native directly. */\n focus(): void {\n this.engine.dispatchCommand(this.node, 'focus');\n }\n\n blur(): void {\n this.engine.dispatchCommand(this.node, 'blur');\n }\n\n /** Empty the field. */\n clear(): void {\n this.value.set('');\n }\n\n /** Move the caret, or select a range. */\n setSelection(start: number, end = start): void {\n this.engine.dispatchCommand(this.node, 'setTextAndSelection', [\n this.eventCount(),\n null,\n start,\n end,\n ]);\n }\n\n /** Whether this field currently has the keyboard. */\n isFocused(): boolean {\n return this.engine.focused === this.node;\n }\n\n protected onNativeChange(event: NativeSyntheticEvent<Partial<TextInputChangePayload>>): void {\n const native = event.nativeEvent;\n const text = native?.text ?? '';\n // Order matters, as it does in RN: the value first, the count last.\n this.lastNativeText = text;\n this.control.propose(text);\n this.eventCount.set(native?.eventCount ?? this.eventCount() + 1);\n this.changeText.emit(text);\n }\n\n /** Send native the text and selection it does not have, as one `setTextAndSelection`. */\n private syncWithNative(): void {\n const desired = this.value();\n const count = this.eventCount();\n const selection = this.resolvedSelection();\n this.selectionReports();\n const textChanged = this.lastNativeText !== null && this.lastNativeText !== desired;\n if (!textChanged && !this.differsFromNative(selection)) return;\n if (textChanged) this.lastNativeText = desired;\n if (selection) this.lastSelection = selection;\n // The `text` prop alone may be ignored as stale, so send it as a command with the count\n // native expects, and the text and bound selection as RN does whichever of them changed.\n this.engine.dispatchCommand(this.node, 'setTextAndSelection', [\n count,\n desired,\n selection?.start ?? -1,\n selection?.end ?? -1,\n ]);\n // Fabric sized the field for the text it had; see `HostEngine.remeasure`.\n if (textChanged) this.engine.remeasure(this.node);\n }\n\n /**\n * Whether a bound selection is not the one native last had. The prop alone does not move the\n * caret: Android has no `selection` prop, and iOS reads it only as the field appears or its\n * text is replaced.\n */\n private differsFromNative(selection: { start: number; end: number } | undefined): boolean {\n return (\n selection !== undefined &&\n (selection.start !== this.lastSelection.start || selection.end !== this.lastSelection.end)\n );\n }\n\n /** What native now has selected, which a bound selection that differs is sent over again. */\n protected onNativeSelectionChange(\n event: NativeSyntheticEvent<Partial<SelectionChangePayload>>,\n ): void {\n const selection = event.nativeEvent?.selection;\n if (!selection) return;\n this.lastSelection = { start: selection.start, end: selection.end };\n this.selectionReports.update((n) => n + 1);\n }\n}\n"]}
@@ -3,15 +3,18 @@ import * as i0 from "@angular/core";
3
3
  /**
4
4
  * A pressable that dims while pressed, as RN's `TouchableOpacity` does.
5
5
  *
6
- * The fade is a CSS transition rather than a JavaScript tween: the component sets the target
7
- * opacity and the engine eases to it. React Native's own timings are asymmetric - 150ms in,
8
- * 250ms out - which is a thing CSS expresses exactly, because the duration in force is the one on
9
- * the state being entered. That is what the two rules below are.
6
+ * The fade is a CSS transition rather than a JavaScript tween: a rule sets the pressed opacity and
7
+ * the engine eases to it. React Native's own timings are asymmetric - 150ms in, 250ms out - which
8
+ * is a thing CSS expresses exactly, because the duration in force is the one on the state being
9
+ * entered. That is what the two rules below are.
10
10
  *
11
- * The opacity is a binding rather than a rule because `activeOpacity` is an input, and it returns
12
- * to `undefined` when the fade finishes so a caller's own `[style]` opacity is in charge again at
13
- * rest. `transitionend` is the engine's own event, the same one Angular's `animate.leave` waits
14
- * for.
11
+ * Nothing here states the resting opacity, so whatever the cascade gives the host is what a press
12
+ * eases back to: a class, Tailwind's, an inline `[style]` or a bound `[style.opacity]`, read when
13
+ * the press ends as `TouchableOpacity.js` reads its style, and followed as it changes. An opacity
14
+ * nothing sets is still 1 to ease from and to. The pressed rule is `!important` because a
15
+ * caller's inline opacity outranks this component's host bindings and plain rules alike, and
16
+ * would otherwise keep it from dimming at all; `activeOpacity` reaches it as a custom property,
17
+ * which no caller's style sets.
15
18
  */
16
19
  export declare class TouchableOpacity extends ControlBase {
17
20
  /** `TouchableOpacity.js`: `accessible={this.props.accessible !== false}`, and focusable too. */
@@ -21,17 +24,6 @@ export declare class TouchableOpacity extends ControlBase {
21
24
  protected pressDisabled(): boolean;
22
25
  /** The opacity while pressed. */
23
26
  readonly activeOpacity: import("@angular/core").InputSignalWithTransform<number, unknown>;
24
- /** Undefined at rest, so the caller's own `[style]` opacity shows through. */
25
- protected readonly opacity: import("@angular/core").WritableSignal<number | undefined>;
26
- /** What to ease back to: whatever the caller had before the press. */
27
- private resting;
28
- constructor();
29
- /**
30
- * The fade is over. Letting go of the value hands the opacity back to whatever the caller's own
31
- * style says, which is the same number we just eased to - so nothing moves, and a caller who
32
- * changes it later is not fighting a binding of ours.
33
- */
34
- protected settle(): void;
35
27
  static ɵfac: i0.ɵɵFactoryDeclaration<TouchableOpacity, never>;
36
28
  static ɵcmp: i0.ɵɵComponentDeclaration<TouchableOpacity, "touchable-opacity", ["touchableOpacity"], { "activeOpacity": { "alias": "activeOpacity"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
37
29
  }
@@ -1,19 +1,22 @@
1
- import { Component, DestroyRef, effect, inject, input, signal, untracked } from '@angular/core';
1
+ import { Component, input } from '@angular/core';
2
2
  import { ControlBase } from "./pressable.js";
3
3
  import { numberOr } from "./transforms.js";
4
4
  import * as i0 from "@angular/core";
5
5
  /**
6
6
  * A pressable that dims while pressed, as RN's `TouchableOpacity` does.
7
7
  *
8
- * The fade is a CSS transition rather than a JavaScript tween: the component sets the target
9
- * opacity and the engine eases to it. React Native's own timings are asymmetric - 150ms in,
10
- * 250ms out - which is a thing CSS expresses exactly, because the duration in force is the one on
11
- * the state being entered. That is what the two rules below are.
8
+ * The fade is a CSS transition rather than a JavaScript tween: a rule sets the pressed opacity and
9
+ * the engine eases to it. React Native's own timings are asymmetric - 150ms in, 250ms out - which
10
+ * is a thing CSS expresses exactly, because the duration in force is the one on the state being
11
+ * entered. That is what the two rules below are.
12
12
  *
13
- * The opacity is a binding rather than a rule because `activeOpacity` is an input, and it returns
14
- * to `undefined` when the fade finishes so a caller's own `[style]` opacity is in charge again at
15
- * rest. `transitionend` is the engine's own event, the same one Angular's `animate.leave` waits
16
- * for.
13
+ * Nothing here states the resting opacity, so whatever the cascade gives the host is what a press
14
+ * eases back to: a class, Tailwind's, an inline `[style]` or a bound `[style.opacity]`, read when
15
+ * the press ends as `TouchableOpacity.js` reads its style, and followed as it changes. An opacity
16
+ * nothing sets is still 1 to ease from and to. The pressed rule is `!important` because a
17
+ * caller's inline opacity outranks this component's host bindings and plain rules alike, and
18
+ * would otherwise keep it from dimming at all; `activeOpacity` reaches it as a custom property,
19
+ * which no caller's style sets.
17
20
  */
18
21
  export class TouchableOpacity extends ControlBase {
19
22
  /** `TouchableOpacity.js`: `accessible={this.props.accessible !== false}`, and focusable too. */
@@ -31,50 +34,14 @@ export class TouchableOpacity extends ControlBase {
31
34
  }
32
35
  /** The opacity while pressed. */
33
36
  activeOpacity = input(0.2, { ...(ngDevMode ? { debugName: "activeOpacity" } : /* istanbul ignore next */ {}), transform: numberOr(0.2) });
34
- /** Undefined at rest, so the caller's own `[style]` opacity shows through. */
35
- opacity = signal(undefined, /* @ts-ignore */
36
- ...(ngDevMode ? [{ debugName: "opacity" }] : /* istanbul ignore next */ []));
37
- /** What to ease back to: whatever the caller had before the press. */
38
- resting = 1;
39
- constructor() {
40
- super();
41
- // Reacts to `pressed` alone. Reading `opacity` here would re-run the effect on the value it
42
- // just wrote, and a press that arrives mid-fade would restart from the wrong end.
43
- effect(() => {
44
- const pressed = this.pressed();
45
- untracked(() => {
46
- if (pressed) {
47
- // Remembered on the way in, so the fade out returns to it rather than to 1.
48
- const style = this.node.props['style'];
49
- this.resting = this.opacity() ?? style?.opacity ?? 1;
50
- this.opacity.set(this.activeOpacity());
51
- }
52
- else if (this.opacity() !== undefined) {
53
- this.opacity.set(this.resting);
54
- }
55
- });
56
- });
57
- // A component destroyed mid-fade leaves a transition the engine would go on advancing.
58
- inject(DestroyRef).onDestroy(() => this.opacity.set(undefined));
59
- }
60
- /**
61
- * The fade is over. Letting go of the value hands the opacity back to whatever the caller's own
62
- * style says, which is the same number we just eased to - so nothing moves, and a caller who
63
- * changes it later is not fighting a binding of ours.
64
- */
65
- settle() {
66
- if (!this.pressed())
67
- this.opacity.set(undefined);
68
- }
69
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: TouchableOpacity, deps: [], target: i0.ɵɵFactoryTarget.Component });
70
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: TouchableOpacity, isStandalone: true, selector: "touchable-opacity", inputs: { activeOpacity: { classPropertyName: "activeOpacity", publicName: "activeOpacity", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "transitionend": "settle()" }, properties: { "style.opacity": "opacity()", "class.pressed": "pressed()" } }, exportAs: ["touchableOpacity"], usesInheritance: true, ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["\n /*\n * The resting opacity is stated rather than left out, because a transition needs a value to\n * ease *from*: a property appearing for the first time is recorded and left alone, which is\n * CSS's own rule and would make every fade in a snap. Inline style still wins over this, so a\n * caller's own opacity is untouched.\n */\n :host {\n opacity: 1;\n transition: opacity 250ms ease-in-out;\n }\n :host(.pressed) {\n transition: opacity 150ms ease-in-out;\n }\n "] });
37
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: TouchableOpacity, deps: null, target: i0.ɵɵFactoryTarget.Component });
38
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: TouchableOpacity, isStandalone: true, selector: "touchable-opacity", inputs: { activeOpacity: { classPropertyName: "activeOpacity", publicName: "activeOpacity", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.--touchable-active-opacity": "activeOpacity()", "class.pressed": "pressed()" } }, exportAs: ["touchableOpacity"], usesInheritance: true, ngImport: i0, template: `<ng-content />`, isInline: true, styles: ["\n :host {\n transition: opacity 250ms ease-in-out;\n }\n :host(.pressed) {\n opacity: var(--touchable-active-opacity, 0.2) !important;\n transition: opacity 150ms ease-in-out;\n }\n "] });
71
39
  }
72
40
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: TouchableOpacity, decorators: [{
73
41
  type: Component,
74
42
  args: [{ selector: 'touchable-opacity', template: `<ng-content />`, exportAs: 'touchableOpacity', host: {
75
- '[style.opacity]': 'opacity()',
43
+ '[style.--touchable-active-opacity]': 'activeOpacity()',
76
44
  '[class.pressed]': 'pressed()',
77
- '(transitionend)': 'settle()',
78
- }, styles: ["\n /*\n * The resting opacity is stated rather than left out, because a transition needs a value to\n * ease *from*: a property appearing for the first time is recorded and left alone, which is\n * CSS's own rule and would make every fade in a snap. Inline style still wins over this, so a\n * caller's own opacity is untouched.\n */\n :host {\n opacity: 1;\n transition: opacity 250ms ease-in-out;\n }\n :host(.pressed) {\n transition: opacity 150ms ease-in-out;\n }\n "] }]
79
- }], ctorParameters: () => [], propDecorators: { activeOpacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeOpacity", required: false }] }] } });
45
+ }, styles: ["\n :host {\n transition: opacity 250ms ease-in-out;\n }\n :host(.pressed) {\n opacity: var(--touchable-active-opacity, 0.2) !important;\n transition: opacity 150ms ease-in-out;\n }\n "] }]
46
+ }], propDecorators: { activeOpacity: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeOpacity", required: false }] }] } });
80
47
  //# sourceMappingURL=touchable-opacity.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"touchable-opacity.js","sourceRoot":"","sources":["../src/touchable-opacity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;;AAE3C;;;;;;;;;;;;GAYG;AA0BH,MAAM,OAAO,gBAAiB,SAAQ,WAAW;IAC/C,gGAAgG;IAC7E,mBAAmB;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAEkB,kBAAkB;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAEkB,wBAAwB;QACzC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;IAEkB,aAAa;QAC9B,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAC;IACnC,CAAC;IAED,iCAAiC;IACxB,aAAa,GAAG,KAAK,CAAC,GAAG,qFAAI,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;IAElE,8EAA8E;IAC3D,OAAO,GAAG,MAAM,CAAqB,SAAS;gFAAC,CAAC;IAEnE,sEAAsE;IAC9D,OAAO,GAAG,CAAC,CAAC;IAEpB;QACE,KAAK,EAAE,CAAC;QACR,4FAA4F;QAC5F,kFAAkF;QAClF,MAAM,CAAC,GAAG,EAAE;YACV,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,EAAE;gBACb,IAAI,OAAO,EAAE,CAAC;oBACZ,4EAA4E;oBAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAqC,CAAC;oBAC3E,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,KAAK,EAAE,OAAO,IAAI,CAAC,CAAC;oBACrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;gBACzC,CAAC;qBAAM,IAAI,IAAI,CAAC,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;oBACxC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACjC,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,uFAAuF;QACvF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IAClE,CAAC;IAED;;;;OAIG;IACO,MAAM;QACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACnD,CAAC;uGAvDU,gBAAgB;2FAAhB,gBAAgB,kaAvBjB,gBAAgB;;2FAuBf,gBAAgB;kBAzB5B,SAAS;+BACE,mBAAmB,YACnB,gBAAgB,YAChB,kBAAkB,QAgBtB;wBACJ,iBAAiB,EAAE,WAAW;wBAC9B,iBAAiB,EAAE,WAAW;wBAC9B,iBAAiB,EAAE,UAAU;qBAC9B","sourcesContent":["import { Component, DestroyRef, effect, inject, input, signal, untracked } from '@angular/core';\nimport { ControlBase } from './pressable.ts';\nimport { numberOr } from './transforms.ts';\n\n/**\n * A pressable that dims while pressed, as RN's `TouchableOpacity` does.\n *\n * The fade is a CSS transition rather than a JavaScript tween: the component sets the target\n * opacity and the engine eases to it. React Native's own timings are asymmetric - 150ms in,\n * 250ms out - which is a thing CSS expresses exactly, because the duration in force is the one on\n * the state being entered. That is what the two rules below are.\n *\n * The opacity is a binding rather than a rule because `activeOpacity` is an input, and it returns\n * to `undefined` when the fade finishes so a caller's own `[style]` opacity is in charge again at\n * rest. `transitionend` is the engine's own event, the same one Angular's `animate.leave` waits\n * for.\n */\n@Component({\n selector: 'touchable-opacity',\n template: `<ng-content />`,\n exportAs: 'touchableOpacity',\n styles: `\n /*\n * The resting opacity is stated rather than left out, because a transition needs a value to\n * ease *from*: a property appearing for the first time is recorded and left alone, which is\n * CSS's own rule and would make every fade in a snap. Inline style still wins over this, so a\n * caller's own opacity is untouched.\n */\n :host {\n opacity: 1;\n transition: opacity 250ms ease-in-out;\n }\n :host(.pressed) {\n transition: opacity 150ms ease-in-out;\n }\n `,\n host: {\n '[style.opacity]': 'opacity()',\n '[class.pressed]': 'pressed()',\n '(transitionend)': 'settle()',\n },\n})\nexport class TouchableOpacity extends ControlBase {\n /** `TouchableOpacity.js`: `accessible={this.props.accessible !== false}`, and focusable too. */\n protected override accessibleByDefault(): boolean {\n return true;\n }\n\n protected override focusableByDefault(): boolean {\n return true;\n }\n\n protected override disabledForAccessibility(): boolean | undefined {\n return this.disabled();\n }\n\n protected override pressDisabled(): boolean {\n return this.disabledByAnyState();\n }\n\n /** The opacity while pressed. */\n readonly activeOpacity = input(0.2, { transform: numberOr(0.2) });\n\n /** Undefined at rest, so the caller's own `[style]` opacity shows through. */\n protected readonly opacity = signal<number | undefined>(undefined);\n\n /** What to ease back to: whatever the caller had before the press. */\n private resting = 1;\n\n constructor() {\n super();\n // Reacts to `pressed` alone. Reading `opacity` here would re-run the effect on the value it\n // just wrote, and a press that arrives mid-fade would restart from the wrong end.\n effect(() => {\n const pressed = this.pressed();\n untracked(() => {\n if (pressed) {\n // Remembered on the way in, so the fade out returns to it rather than to 1.\n const style = this.node.props['style'] as { opacity?: number } | undefined;\n this.resting = this.opacity() ?? style?.opacity ?? 1;\n this.opacity.set(this.activeOpacity());\n } else if (this.opacity() !== undefined) {\n this.opacity.set(this.resting);\n }\n });\n });\n // A component destroyed mid-fade leaves a transition the engine would go on advancing.\n inject(DestroyRef).onDestroy(() => this.opacity.set(undefined));\n }\n\n /**\n * The fade is over. Letting go of the value hands the opacity back to whatever the caller's own\n * style says, which is the same number we just eased to - so nothing moves, and a caller who\n * changes it later is not fighting a binding of ours.\n */\n protected settle(): void {\n if (!this.pressed()) this.opacity.set(undefined);\n }\n}\n"]}
1
+ {"version":3,"file":"touchable-opacity.js","sourceRoot":"","sources":["../src/touchable-opacity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;;AAE3C;;;;;;;;;;;;;;;GAeG;AAmBH,MAAM,OAAO,gBAAiB,SAAQ,WAAW;IAC/C,gGAAgG;IAC7E,mBAAmB;QACpC,OAAO,IAAI,CAAC;IACd,CAAC;IAEkB,kBAAkB;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAEkB,wBAAwB;QACzC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;IAEkB,aAAa;QAC9B,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAC;IACnC,CAAC;IAED,iCAAiC;IACxB,aAAa,GAAG,KAAK,CAAC,GAAG,qFAAI,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;uGAnBvD,gBAAgB;2FAAhB,gBAAgB,+YAhBjB,gBAAgB;;2FAgBf,gBAAgB;kBAlB5B,SAAS;+BACE,mBAAmB,YACnB,gBAAgB,YAChB,kBAAkB,QAUtB;wBACJ,oCAAoC,EAAE,iBAAiB;wBACvD,iBAAiB,EAAE,WAAW;qBAC/B","sourcesContent":["import { Component, input } from '@angular/core';\nimport { ControlBase } from './pressable.ts';\nimport { numberOr } from './transforms.ts';\n\n/**\n * A pressable that dims while pressed, as RN's `TouchableOpacity` does.\n *\n * The fade is a CSS transition rather than a JavaScript tween: a rule sets the pressed opacity and\n * the engine eases to it. React Native's own timings are asymmetric - 150ms in, 250ms out - which\n * is a thing CSS expresses exactly, because the duration in force is the one on the state being\n * entered. That is what the two rules below are.\n *\n * Nothing here states the resting opacity, so whatever the cascade gives the host is what a press\n * eases back to: a class, Tailwind's, an inline `[style]` or a bound `[style.opacity]`, read when\n * the press ends as `TouchableOpacity.js` reads its style, and followed as it changes. An opacity\n * nothing sets is still 1 to ease from and to. The pressed rule is `!important` because a\n * caller's inline opacity outranks this component's host bindings and plain rules alike, and\n * would otherwise keep it from dimming at all; `activeOpacity` reaches it as a custom property,\n * which no caller's style sets.\n */\n@Component({\n selector: 'touchable-opacity',\n template: `<ng-content />`,\n exportAs: 'touchableOpacity',\n styles: `\n :host {\n transition: opacity 250ms ease-in-out;\n }\n :host(.pressed) {\n opacity: var(--touchable-active-opacity, 0.2) !important;\n transition: opacity 150ms ease-in-out;\n }\n `,\n host: {\n '[style.--touchable-active-opacity]': 'activeOpacity()',\n '[class.pressed]': 'pressed()',\n },\n})\nexport class TouchableOpacity extends ControlBase {\n /** `TouchableOpacity.js`: `accessible={this.props.accessible !== false}`, and focusable too. */\n protected override accessibleByDefault(): boolean {\n return true;\n }\n\n protected override focusableByDefault(): boolean {\n return true;\n }\n\n protected override disabledForAccessibility(): boolean | undefined {\n return this.disabled();\n }\n\n protected override pressDisabled(): boolean {\n return this.disabledByAnyState();\n }\n\n /** The opacity while pressed. */\n readonly activeOpacity = input(0.2, { transform: numberOr(0.2) });\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ng-native/components",
3
- "version": "0.17.1",
3
+ "version": "0.18.1",
4
4
  "description": "Angular Native's core components: view, text, image, pressable, scroll-view, virtual-list, text-input and the rest of React Native's set.",
5
5
  "keywords": [
6
6
  "angular",
@@ -39,6 +39,10 @@
39
39
  "browser": "./dist/reanimated-web.js",
40
40
  "default": "./dist/reanimated.js"
41
41
  },
42
+ "./stand-ins/animations": {
43
+ "types": "./dist/stand-ins/animations.d.ts",
44
+ "default": "./dist/stand-ins/animations.js"
45
+ },
42
46
  "./stand-ins/reanimated": {
43
47
  "types": "./dist/stand-ins/reanimated.d.ts",
44
48
  "default": "./dist/stand-ins/reanimated.js"
@@ -57,8 +61,8 @@
57
61
  "access": "public"
58
62
  },
59
63
  "dependencies": {
60
- "@ng-native/device": "0.17.1",
61
- "@ng-native/fabric": "0.17.1"
64
+ "@ng-native/device": "0.18.1",
65
+ "@ng-native/fabric": "0.18.1"
62
66
  },
63
67
  "peerDependencies": {
64
68
  "@angular/core": "^22.0.0",