@ng-native/components 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +78 -1
  3. package/dist/activity-indicator.d.ts +41 -0
  4. package/dist/activity-indicator.js +66 -0
  5. package/dist/activity-indicator.js.map +1 -0
  6. package/dist/animated-style.d.ts +31 -0
  7. package/dist/animated-style.js +102 -0
  8. package/dist/animated-style.js.map +1 -0
  9. package/dist/animated.d.ts +125 -0
  10. package/dist/animated.js +538 -0
  11. package/dist/animated.js.map +1 -0
  12. package/dist/animation.d.ts +40 -0
  13. package/dist/animation.js +20 -0
  14. package/dist/animation.js.map +1 -0
  15. package/dist/animations-web.d.ts +8 -0
  16. package/dist/animations-web.js +31 -0
  17. package/dist/animations-web.js.map +1 -0
  18. package/dist/animations.d.ts +7 -0
  19. package/dist/animations.js +58 -0
  20. package/dist/animations.js.map +1 -0
  21. package/dist/controlled-model.d.ts +33 -0
  22. package/dist/controlled-model.js +47 -0
  23. package/dist/controlled-model.js.map +1 -0
  24. package/dist/easing.d.ts +21 -0
  25. package/dist/easing.js +50 -0
  26. package/dist/easing.js.map +1 -0
  27. package/dist/events.d.ts +169 -0
  28. package/dist/events.js +2 -0
  29. package/dist/events.js.map +1 -0
  30. package/dist/gesture-backend.d.ts +26 -0
  31. package/dist/gesture-backend.js +14 -0
  32. package/dist/gesture-backend.js.map +1 -0
  33. package/dist/gesture-dispatch.d.ts +67 -0
  34. package/dist/gesture-dispatch.js +106 -0
  35. package/dist/gesture-dispatch.js.map +1 -0
  36. package/dist/gestures.d.ts +23 -0
  37. package/dist/gestures.js +164 -0
  38. package/dist/gestures.js.map +1 -0
  39. package/dist/height-index.d.ts +31 -0
  40. package/dist/height-index.js +80 -0
  41. package/dist/height-index.js.map +1 -0
  42. package/dist/image-background.d.ts +32 -0
  43. package/dist/image-background.js +80 -0
  44. package/dist/image-background.js.map +1 -0
  45. package/dist/image.d.ts +79 -0
  46. package/dist/image.js +155 -0
  47. package/dist/image.js.map +1 -0
  48. package/dist/index.d.ts +38 -0
  49. package/dist/index.js +39 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/input-accessory-view.d.ts +16 -0
  52. package/dist/input-accessory-view.js +29 -0
  53. package/dist/input-accessory-view.js.map +1 -0
  54. package/dist/keyboard-avoiding-view.d.ts +79 -0
  55. package/dist/keyboard-avoiding-view.js +172 -0
  56. package/dist/keyboard-avoiding-view.js.map +1 -0
  57. package/dist/keyboard-dock.d.ts +126 -0
  58. package/dist/keyboard-dock.js +327 -0
  59. package/dist/keyboard-dock.js.map +1 -0
  60. package/dist/keyboard-taps.d.ts +12 -0
  61. package/dist/keyboard-taps.js +51 -0
  62. package/dist/keyboard-taps.js.map +1 -0
  63. package/dist/modal.d.ts +56 -0
  64. package/dist/modal.js +102 -0
  65. package/dist/modal.js.map +1 -0
  66. package/dist/native-gesture.d.ts +21 -0
  67. package/dist/native-gesture.js +56 -0
  68. package/dist/native-gesture.js.map +1 -0
  69. package/dist/native-ref.d.ts +7 -0
  70. package/dist/native-ref.js +13 -0
  71. package/dist/native-ref.js.map +1 -0
  72. package/dist/pressable.d.ts +220 -0
  73. package/dist/pressable.js +484 -0
  74. package/dist/pressable.js.map +1 -0
  75. package/dist/reanimated.d.ts +19 -0
  76. package/dist/reanimated.js +111 -0
  77. package/dist/reanimated.js.map +1 -0
  78. package/dist/refresh-control.d.ts +50 -0
  79. package/dist/refresh-control.js +103 -0
  80. package/dist/refresh-control.js.map +1 -0
  81. package/dist/safe-area-provider.d.ts +11 -0
  82. package/dist/safe-area-provider.js +70 -0
  83. package/dist/safe-area-provider.js.map +1 -0
  84. package/dist/safe-area-view.d.ts +22 -0
  85. package/dist/safe-area-view.js +77 -0
  86. package/dist/safe-area-view.js.map +1 -0
  87. package/dist/safe-area.d.ts +12 -0
  88. package/dist/safe-area.js +27 -0
  89. package/dist/safe-area.js.map +1 -0
  90. package/dist/scroll-view-props.d.ts +89 -0
  91. package/dist/scroll-view-props.js +153 -0
  92. package/dist/scroll-view-props.js.map +1 -0
  93. package/dist/scroll-view.d.ts +95 -0
  94. package/dist/scroll-view.js +181 -0
  95. package/dist/scroll-view.js.map +1 -0
  96. package/dist/section-list.d.ts +146 -0
  97. package/dist/section-list.js +300 -0
  98. package/dist/section-list.js.map +1 -0
  99. package/dist/sticky-headers.d.ts +41 -0
  100. package/dist/sticky-headers.js +118 -0
  101. package/dist/sticky-headers.js.map +1 -0
  102. package/dist/swipe-refresh-layout.d.ts +51 -0
  103. package/dist/swipe-refresh-layout.js +162 -0
  104. package/dist/swipe-refresh-layout.js.map +1 -0
  105. package/dist/switch.d.ts +54 -0
  106. package/dist/switch.js +110 -0
  107. package/dist/switch.js.map +1 -0
  108. package/dist/template-slot.d.ts +18 -0
  109. package/dist/template-slot.js +30 -0
  110. package/dist/template-slot.js.map +1 -0
  111. package/dist/text-input.d.ts +168 -0
  112. package/dist/text-input.js +312 -0
  113. package/dist/text-input.js.map +1 -0
  114. package/dist/text.d.ts +70 -0
  115. package/dist/text.js +111 -0
  116. package/dist/text.js.map +1 -0
  117. package/dist/touchable-opacity.d.ts +36 -0
  118. package/dist/touchable-opacity.js +76 -0
  119. package/dist/touchable-opacity.js.map +1 -0
  120. package/dist/transforms.d.ts +2 -0
  121. package/dist/transforms.js +17 -0
  122. package/dist/transforms.js.map +1 -0
  123. package/dist/view-base.d.ts +332 -0
  124. package/dist/view-base.js +534 -0
  125. package/dist/view-base.js.map +1 -0
  126. package/dist/view.d.ts +12 -0
  127. package/dist/view.js +18 -0
  128. package/dist/view.js.map +1 -0
  129. package/dist/virtual-list.d.ts +530 -0
  130. package/dist/virtual-list.js +1150 -0
  131. package/dist/virtual-list.js.map +1 -0
  132. package/dist/worklet-scroll.d.ts +13 -0
  133. package/dist/worklet-scroll.js +49 -0
  134. package/dist/worklet-scroll.js.map +1 -0
  135. package/dist/worklet-style.d.ts +13 -0
  136. package/dist/worklet-style.js +47 -0
  137. package/dist/worklet-style.js.map +1 -0
  138. package/dist/worklets.d.ts +89 -0
  139. package/dist/worklets.js +42 -0
  140. package/dist/worklets.js.map +1 -0
  141. package/dist/written-props.d.ts +6 -0
  142. package/dist/written-props.js +180 -0
  143. package/dist/written-props.js.map +1 -0
  144. package/package.json +88 -3
@@ -0,0 +1,172 @@
1
+ import { Component, booleanAttribute, computed, effect, inject, input, numberAttribute, signal, } from '@angular/core';
2
+ import { Keyboard, LayoutAnimation } from '@ng-native/device';
3
+ import { View } from "./view.js";
4
+ import { ViewBase } from "./view-base.js";
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * The names `LayoutEasing` knows. `'keyboard'` is among them: iOS reports its keyboard's own
8
+ * curve by that name, and RN's KeyboardAvoidingView animates on it.
9
+ */
10
+ const LAYOUT_EASINGS = new Set([
11
+ 'linear',
12
+ 'easeInEaseOut',
13
+ 'easeIn',
14
+ 'easeOut',
15
+ 'spring',
16
+ 'keyboard',
17
+ ]);
18
+ /** A keyboard event's `easing`, narrowed to what `LayoutAnimation` accepts, or left to its default. */
19
+ function layoutEasingOf(easing) {
20
+ return easing !== undefined && LAYOUT_EASINGS.has(easing) ? easing : undefined;
21
+ }
22
+ /**
23
+ * Moves its content clear of the keyboard.
24
+ *
25
+ * RN's KeyboardAvoidingView is a JS composite: there is no native component, only a view whose
26
+ * padding, height or position tracks the keyboard. Off a device the `Keyboard` service reports
27
+ * nothing, so this renders as a plain view and does nothing, which is also the right behaviour on
28
+ * a platform with no soft keyboard.
29
+ *
30
+ * The overlap is the view's own frame against the keyboard's top edge, so a view that does not
31
+ * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is
32
+ * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in
33
+ * the parent's coordinates, and on a screen under a navigation bar those start a bar's height
34
+ * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by
35
+ * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the
36
+ * view's own height either: padding a view by more than it has pushes its content out of it.
37
+ * When the keyboard source reports no `screenY`, the keyboard's height stands in.
38
+ *
39
+ * The adjustment itself animates with the keyboard's own motion, the way RN's own
40
+ * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next
41
+ * layout animation before the style change that moves this view, so the two run together rather
42
+ * than the padding, height or position jumping to its resting place in one step.
43
+ */
44
+ export class KeyboardAvoidingView extends ViewBase {
45
+ /**
46
+ * How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this
47
+ * unset and does nothing; `padding` is the default here because that is what a form wants.
48
+ */
49
+ behavior = input('padding', /* @ts-ignore */
50
+ ...(ngDevMode ? [{ debugName: "behavior" }] : /* istanbul ignore next */ []));
51
+ /** Styles for the inner view that `position` moves. */
52
+ contentContainerStyle = input(/* @ts-ignore */
53
+ ...(ngDevMode ? [undefined, { debugName: "contentContainerStyle" }] : /* istanbul ignore next */ []));
54
+ /** Set to false to stop avoiding without removing the view. */
55
+ enabled = input(true, { ...(ngDevMode ? { debugName: "enabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
56
+ /**
57
+ * Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a
58
+ * header's height; here the view is measured in the window, so a navigation bar needs none.
59
+ */
60
+ keyboardVerticalOffset = input(0, { ...(ngDevMode ? { debugName: "keyboardVerticalOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
61
+ keyboard = inject(Keyboard).metrics;
62
+ layoutAnimation = inject(LayoutAnimation);
63
+ frame = signal(null, /* @ts-ignore */
64
+ ...(ngDevMode ? [{ debugName: "frame" }] : /* istanbul ignore next */ []));
65
+ /** The height before any adjustment, which `height` mode shrinks from. */
66
+ initialHeight = null;
67
+ /** How far the keyboard intrudes into this view, in points. */
68
+ overlap = computed(() => {
69
+ if (!this.enabled())
70
+ return 0;
71
+ const { height, screenY } = this.keyboard();
72
+ if (height === 0)
73
+ return 0;
74
+ const frame = this.frame();
75
+ const offset = this.keyboardVerticalOffset();
76
+ if (!frame)
77
+ return Math.max(0, height - offset);
78
+ // In `height` mode the frame is the one this view already shrank, so measure the height it
79
+ // had before, as RN adds its current adjustment back; otherwise the room it made reads as no
80
+ // overlap and the height springs back under the keyboard.
81
+ const extent = this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;
82
+ const overlap = screenY === undefined ? height - offset : frame.y + extent - (screenY - offset);
83
+ return Math.min(extent, Math.max(0, overlap));
84
+ }, /* @ts-ignore */
85
+ ...(ngDevMode ? [{ debugName: "overlap" }] : /* istanbul ignore next */ []));
86
+ /**
87
+ * The adjustment, or nothing to adjust. Bound as the engine's style override rather than as
88
+ * host style, because Angular ranks a host style binding under the template's: a caller's
89
+ * `[style]="{flex: 1}"` would otherwise keep its flex in height mode and the view would not
90
+ * shrink at all. RN composes the adjustment after the caller's style, so it wins there too.
91
+ */
92
+ avoidance = computed(() => {
93
+ const overlap = this.overlap();
94
+ const behavior = this.behavior();
95
+ if (overlap === 0 || behavior === 'position')
96
+ return undefined;
97
+ if (behavior === 'height') {
98
+ const base = this.initialHeight ?? this.frame()?.height;
99
+ return base === undefined ? undefined : { height: base - overlap, flex: 0 };
100
+ }
101
+ return { paddingBottom: overlap };
102
+ }, /* @ts-ignore */
103
+ ...(ngDevMode ? [{ debugName: "avoidance" }] : /* istanbul ignore next */ []));
104
+ positioned = computed(() => ({
105
+ ...this.contentContainerStyle(),
106
+ bottom: this.overlap(),
107
+ }), /* @ts-ignore */
108
+ ...(ngDevMode ? [{ debugName: "positioned" }] : /* istanbul ignore next */ []));
109
+ constructor() {
110
+ super();
111
+ // A duration of zero is Android's own keyboard event, which animates itself; there is
112
+ // nothing to configure alongside it. `appear`/`leave` are turned off because this is an
113
+ // update to a view already on screen, not one appearing or leaving - RN's own bare
114
+ // `LayoutAnimation.configureNext({duration, update})` animates nothing else either.
115
+ effect(() => {
116
+ const { duration, easing } = this.keyboard();
117
+ if (!this.enabled() || !duration)
118
+ return;
119
+ void this.layoutAnimation.animate(() => { }, {
120
+ duration,
121
+ easing: layoutEasingOf(easing),
122
+ appear: 'none',
123
+ leave: 'none',
124
+ });
125
+ });
126
+ }
127
+ onLayout(event) {
128
+ const layout = event.nativeEvent?.layout;
129
+ if (!layout)
130
+ return;
131
+ this.frame.set({ ...layout, y: this.windowTop(layout.y) });
132
+ // Only a layout that the keyboard did not cause counts as the natural height.
133
+ if (this.overlap() === 0)
134
+ this.initialHeight = layout.height;
135
+ }
136
+ /**
137
+ * Where this view's top edge is in the window, or `fallback` where the platform cannot say.
138
+ * Synchronous on the new architecture, so it answers for the layout being reported.
139
+ */
140
+ windowTop(fallback) {
141
+ let top = fallback;
142
+ this.engine.measure(this.node, (frame) => (top = frame.y));
143
+ return top;
144
+ }
145
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardAvoidingView, deps: [], target: i0.ɵɵFactoryTarget.Component });
146
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: KeyboardAvoidingView, isStandalone: true, selector: "keyboard-avoiding-view", inputs: { behavior: { classPropertyName: "behavior", publicName: "behavior", isSignal: true, isRequired: false, transformFunction: null }, contentContainerStyle: { classPropertyName: "contentContainerStyle", publicName: "contentContainerStyle", isSignal: true, isRequired: false, transformFunction: null }, enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, keyboardVerticalOffset: { classPropertyName: "keyboardVerticalOffset", publicName: "keyboardVerticalOffset", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "layout": "onLayout($event)" }, properties: { "styleOverride": "avoidance()" } }, usesInheritance: true, ngImport: i0, template: `
147
+ @if (behavior() === 'position') {
148
+ <view [style]="positioned()"><ng-content /></view>
149
+ } @else {
150
+ <ng-content />
151
+ }
152
+ `, isInline: true, dependencies: [{ kind: "directive", type: View, selector: "view" }] });
153
+ }
154
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardAvoidingView, decorators: [{
155
+ type: Component,
156
+ args: [{
157
+ selector: 'keyboard-avoiding-view',
158
+ imports: [View],
159
+ template: `
160
+ @if (behavior() === 'position') {
161
+ <view [style]="positioned()"><ng-content /></view>
162
+ } @else {
163
+ <ng-content />
164
+ }
165
+ `,
166
+ host: {
167
+ '[styleOverride]': 'avoidance()',
168
+ '(layout)': 'onLayout($event)',
169
+ },
170
+ }]
171
+ }], ctorParameters: () => [], propDecorators: { behavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "behavior", required: false }] }], contentContainerStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "contentContainerStyle", required: false }] }], enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "enabled", required: false }] }], keyboardVerticalOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardVerticalOffset", required: false }] }] } });
172
+ //# sourceMappingURL=keyboard-avoiding-view.js.map
@@ -0,0 +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 booleanAttribute,\n computed,\n effect,\n inject,\n input,\n numberAttribute,\n signal,\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 { View } from './view.ts';\nimport { ViewBase } from './view-base.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.\n */\n@Component({\n selector: 'keyboard-avoiding-view',\n imports: [View],\n template: `\n @if (behavior() === 'position') {\n <view [style]=\"positioned()\"><ng-content /></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 /** 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: numberAttribute });\n\n private readonly keyboard = inject(Keyboard).metrics;\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 animates itself; there is\n // nothing to configure alongside it. `appear`/`leave` are turned off because this is an\n // update to a view already on screen, not one appearing or leaving - RN's own bare\n // `LayoutAnimation.configureNext({duration, update})` animates nothing else either.\n effect(() => {\n const { duration, easing } = this.keyboard();\n if (!this.enabled() || !duration) 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"]}
@@ -0,0 +1,126 @@
1
+ import { InjectionToken, type Provider } from '@angular/core';
2
+ import { type HostNode, type ScrollDrive, type ScrollRange } from '@ng-native/fabric';
3
+ import { ViewBase } from './view-base.ts';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`
7
+ * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.
8
+ */
9
+ export declare const KEYBOARD_CONTROLLER: InjectionToken<boolean>;
10
+ /**
11
+ * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.
12
+ * Nothing is imported from it: the dock drives its native views directly.
13
+ */
14
+ export declare function provideKeyboardController(): Provider;
15
+ /** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */
16
+ export declare class KeyboardControllerView {
17
+ constructor();
18
+ static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardControllerView, never>;
19
+ static ɵdir: i0.ɵɵDirectiveDeclaration<KeyboardControllerView, "keyboard-controller-view", never, {}, {}, never, never, true, never>;
20
+ }
21
+ /**
22
+ * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with
23
+ * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.
24
+ */
25
+ export declare class KeyboardGestureArea {
26
+ readonly offset: import("@angular/core").InputSignal<number | undefined>;
27
+ readonly textInputNativeID: import("@angular/core").InputSignal<string | undefined>;
28
+ constructor();
29
+ static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardGestureArea, never>;
30
+ static ɵdir: i0.ɵɵDirectiveDeclaration<KeyboardGestureArea, "keyboard-gesture-area", never, { "offset": { "alias": "offset"; "required": false; "isSignal": true; }; "textInputNativeID": { "alias": "textInputNativeID"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
31
+ }
32
+ /**
33
+ * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.
34
+ * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard
35
+ * is down; on top of it while it is up.
36
+ *
37
+ * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved
38
+ * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input
39
+ * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll
40
+ * view with `keyboardDismissMode="interactive"`, where iOS reports no keyboard frames to
41
+ * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of
42
+ * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves
43
+ * content above with it.
44
+ *
45
+ * Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it
46
+ * learns as the keyboard starts to move; a window the system resized for the keyboard already
47
+ * puts it clear, and then it is not padded at all.
48
+ *
49
+ * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
50
+ * place, for content above that keeps clear of it with padding or an inset rather than moving.
51
+ */
52
+ export declare class KeyboardDock extends ViewBase {
53
+ /** The bar's background, under the inset it sits above as well as the bar itself. */
54
+ readonly backgroundColor: import("@angular/core").InputSignal<string | undefined>;
55
+ private readonly keyboard;
56
+ private readonly safeArea;
57
+ protected readonly none: {};
58
+ protected readonly outOfFlow: {
59
+ position: string;
60
+ width: number;
61
+ height: number;
62
+ };
63
+ /** iOS with the controller: native moves the bar. Read once: neither changes under an app. */
64
+ protected readonly lifts: boolean;
65
+ private readonly field;
66
+ private readonly fieldInput;
67
+ /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */
68
+ private readonly bottomEdge;
69
+ /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */
70
+ protected readonly barHeight: import("@angular/core").WritableSignal<number>;
71
+ /** The keyboard's height as the controller last reported it, excluding the drag area. */
72
+ private readonly controlled;
73
+ /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */
74
+ private readonly inset;
75
+ readonly covered: import("@angular/core").Signal<number>;
76
+ protected readonly padding: import("@angular/core").Signal<number>;
77
+ /**
78
+ * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field
79
+ * without one is given one.
80
+ */
81
+ protected readonly inputID: import("@angular/core").Signal<string | undefined>;
82
+ /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */
83
+ readonly liftRange: import("@angular/core").Signal<ScrollRange>;
84
+ private source;
85
+ private readonly inFront;
86
+ constructor();
87
+ /**
88
+ * Move a view with the keyboard on the native side, as the bar is moved: content above the bar
89
+ * that should rise with it. Null when native does not move the bar. See `keyboardLift`.
90
+ */
91
+ lift(view: HostNode, shift?: number | null): ScrollDrive | null;
92
+ private liftBar;
93
+ /** The controller's view, the one whose events carry the keyboard. */
94
+ private controllerView;
95
+ /** The view holding the content, the last of the dock's own. */
96
+ private barView;
97
+ protected onKeyboardMove(event: unknown): void;
98
+ protected onBarLayout(event: {
99
+ nativeEvent?: {
100
+ layout?: {
101
+ height?: number;
102
+ };
103
+ };
104
+ }): void;
105
+ protected onLayout(): void;
106
+ static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardDock, never>;
107
+ static ɵcmp: i0.ɵɵComponentDeclaration<KeyboardDock, "keyboard-dock", ["keyboardDock"], { "backgroundColor": { "alias": "backgroundColor"; "required": false; "isSignal": true; }; }, {}, ["field", "fieldInput"], ["*"], true, never>;
108
+ }
109
+ /**
110
+ * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]="dock">` around a chat's
111
+ * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is
112
+ * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than
113
+ * drawn over what is above. Does nothing where native does not move the bar.
114
+ *
115
+ * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved
116
+ * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view
117
+ * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,
118
+ * and a transform does not change what it thinks.
119
+ */
120
+ export declare class KeyboardLift {
121
+ readonly dock: import("@angular/core").InputSignal<KeyboardDock>;
122
+ protected readonly padding: import("@angular/core").Signal<number | undefined>;
123
+ constructor();
124
+ static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardLift, never>;
125
+ static ɵdir: i0.ɵɵDirectiveDeclaration<KeyboardLift, "[keyboardLift]", never, { "dock": { "alias": "keyboardLift"; "required": true; "isSignal": true; }; }, {}, never, never, true, never>;
126
+ }
@@ -0,0 +1,327 @@
1
+ import { Component, DestroyRef, Directive, ElementRef, EnvironmentInjector, InjectionToken, afterRenderEffect, computed, contentChild, effect, inject, input, signal, untracked, } from '@angular/core';
2
+ import { Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';
3
+ import { claimHost, nativePlatform, registerViewName, } from '@ng-native/fabric';
4
+ import { TemplateSlot } from "./template-slot.js";
5
+ import { TextInput } from "./text-input.js";
6
+ import { View } from "./view.js";
7
+ import { ViewBase } from "./view-base.js";
8
+ import * as i0 from "@angular/core";
9
+ /**
10
+ * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`
11
+ * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.
12
+ */
13
+ export const KEYBOARD_CONTROLLER = new InjectionToken('KEYBOARD_CONTROLLER', {
14
+ providedIn: 'root',
15
+ factory: () => false,
16
+ });
17
+ /**
18
+ * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.
19
+ * Nothing is imported from it: the dock drives its native views directly.
20
+ */
21
+ export function provideKeyboardController() {
22
+ return { provide: KEYBOARD_CONTROLLER, useValue: true };
23
+ }
24
+ /** What carries the keyboard's height, frame by frame, in react-native-keyboard-controller. */
25
+ const KEYBOARD_FEED = {
26
+ events: ['onKeyboardMove', 'onKeyboardMoveInteractive', 'onKeyboardMoveEnd'],
27
+ path: ['height'],
28
+ };
29
+ /** Far enough that no keyboard is taller; native interpolation takes numbers, not infinity. */
30
+ const FAR = 10_000;
31
+ /** A native view the dock renders for itself, out of the way of the layout. */
32
+ const OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };
33
+ /** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */
34
+ export class KeyboardControllerView {
35
+ constructor() {
36
+ registerViewName('keyboard-controller-view', 'KeyboardControllerView');
37
+ claimHost(inject(ElementRef).nativeElement);
38
+ }
39
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardControllerView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
40
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.2.0", type: KeyboardControllerView, isStandalone: true, selector: "keyboard-controller-view", host: { properties: { "enabled": "true" } }, ngImport: i0 });
41
+ }
42
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardControllerView, decorators: [{
43
+ type: Directive,
44
+ args: [{
45
+ selector: 'keyboard-controller-view',
46
+ host: { '[enabled]': 'true' },
47
+ }]
48
+ }], ctorParameters: () => [] });
49
+ /**
50
+ * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with
51
+ * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.
52
+ */
53
+ export class KeyboardGestureArea {
54
+ offset = input(/* @ts-ignore */
55
+ ...(ngDevMode ? [undefined, { debugName: "offset" }] : /* istanbul ignore next */ []));
56
+ textInputNativeID = input(/* @ts-ignore */
57
+ ...(ngDevMode ? [undefined, { debugName: "textInputNativeID" }] : /* istanbul ignore next */ []));
58
+ constructor() {
59
+ registerViewName('keyboard-gesture-area', 'KeyboardGestureArea');
60
+ claimHost(inject(ElementRef).nativeElement);
61
+ }
62
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardGestureArea, deps: [], target: i0.ɵɵFactoryTarget.Directive });
63
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: KeyboardGestureArea, isStandalone: true, selector: "keyboard-gesture-area", inputs: { offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, textInputNativeID: { classPropertyName: "textInputNativeID", publicName: "textInputNativeID", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "offset": "offset()", "textInputNativeID": "textInputNativeID()" } }, ngImport: i0 });
64
+ }
65
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardGestureArea, decorators: [{
66
+ type: Directive,
67
+ args: [{
68
+ selector: 'keyboard-gesture-area',
69
+ host: { '[offset]': 'offset()', '[textInputNativeID]': 'textInputNativeID()' },
70
+ }]
71
+ }], ctorParameters: () => [], propDecorators: { offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], textInputNativeID: [{ type: i0.Input, args: [{ isSignal: true, alias: "textInputNativeID", required: false }] }] } });
72
+ /**
73
+ * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.
74
+ * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard
75
+ * is down; on top of it while it is up.
76
+ *
77
+ * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved
78
+ * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input
79
+ * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll
80
+ * view with `keyboardDismissMode="interactive"`, where iOS reports no keyboard frames to
81
+ * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of
82
+ * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves
83
+ * content above with it.
84
+ *
85
+ * Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it
86
+ * learns as the keyboard starts to move; a window the system resized for the keyboard already
87
+ * puts it clear, and then it is not padded at all.
88
+ *
89
+ * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
90
+ * place, for content above that keeps clear of it with padding or an inset rather than moving.
91
+ */
92
+ export class KeyboardDock extends ViewBase {
93
+ /** The bar's background, under the inset it sits above as well as the bar itself. */
94
+ backgroundColor = input(/* @ts-ignore */
95
+ ...(ngDevMode ? [undefined, { debugName: "backgroundColor" }] : /* istanbul ignore next */ []));
96
+ keyboard = inject(Keyboard);
97
+ safeArea = inject(SafeArea);
98
+ none = {};
99
+ outOfFlow = OUT_OF_FLOW;
100
+ /** iOS with the controller: native moves the bar. Read once: neither changes under an app. */
101
+ lifts = nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER);
102
+ field = contentChild(TextInput, { ...(ngDevMode ? { debugName: "field" } : /* istanbul ignore next */ {}), descendants: true, read: ElementRef });
103
+ fieldInput = contentChild(TextInput, { ...(ngDevMode ? { debugName: "fieldInput" } : /* istanbul ignore next */ {}), descendants: true });
104
+ /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */
105
+ bottomEdge = signal(null, /* @ts-ignore */
106
+ ...(ngDevMode ? [{ debugName: "bottomEdge" }] : /* istanbul ignore next */ []));
107
+ /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */
108
+ barHeight = signal(0, /* @ts-ignore */
109
+ ...(ngDevMode ? [{ debugName: "barHeight" }] : /* istanbul ignore next */ []));
110
+ /** The keyboard's height as the controller last reported it, excluding the drag area. */
111
+ controlled = signal(0, /* @ts-ignore */
112
+ ...(ngDevMode ? [{ debugName: "controlled" }] : /* istanbul ignore next */ []));
113
+ /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */
114
+ inset = computed(() => this.safeArea.insets().bottom, /* @ts-ignore */
115
+ ...(ngDevMode ? [{ debugName: "inset" }] : /* istanbul ignore next */ []));
116
+ covered = computed(() => this.lifts ? Math.max(0, this.controlled() - this.inset()) : 0, /* @ts-ignore */
117
+ ...(ngDevMode ? [{ debugName: "covered" }] : /* istanbul ignore next */ []));
118
+ padding = computed(() => {
119
+ if (this.lifts)
120
+ return this.inset();
121
+ const { height, screenY } = this.keyboard.metrics();
122
+ if (height === 0)
123
+ return this.inset();
124
+ const edge = this.bottomEdge();
125
+ if (edge === null || screenY === undefined)
126
+ return height;
127
+ return Math.max(0, edge - screenY);
128
+ }, /* @ts-ignore */
129
+ ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
130
+ /**
131
+ * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field
132
+ * without one is given one.
133
+ */
134
+ inputID = computed(() => {
135
+ const input = this.fieldInput();
136
+ const node = this.field()?.nativeElement;
137
+ if (!input || !node)
138
+ return undefined;
139
+ const own = input.nativeID() ?? input.id();
140
+ if (own)
141
+ return own;
142
+ const id = `keyboard-dock-${++docks}`;
143
+ untracked(() => this.engine.setProp(node, 'nativeID', id));
144
+ return id;
145
+ }, /* @ts-ignore */
146
+ ...(ngDevMode ? [{ debugName: "inputID" }] : /* istanbul ignore next */ []));
147
+ /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */
148
+ liftRange = computed(() => {
149
+ const inset = this.inset();
150
+ return { input: [inset, inset + FAR], output: [0, -FAR] };
151
+ }, /* @ts-ignore */
152
+ ...(ngDevMode ? [{ debugName: "liftRange" }] : /* istanbul ignore next */ []));
153
+ source = null;
154
+ inFront = inject(SCREEN_IN_FRONT);
155
+ constructor() {
156
+ super();
157
+ if (this.lifts)
158
+ this.liftBar();
159
+ // A root effect, because a covered screen's views are detached from change detection, and a
160
+ // view's own effects wait for it.
161
+ const front = effect(() => {
162
+ const inFront = this.inFront();
163
+ untracked(() => {
164
+ if (!inFront && this.keyboard.height() > 0)
165
+ this.keyboard.dismiss();
166
+ });
167
+ }, { ...(ngDevMode ? { debugName: "front" } : /* istanbul ignore next */ {}), injector: inject(EnvironmentInjector), manualCleanup: true });
168
+ inject(DestroyRef).onDestroy(() => front.destroy());
169
+ }
170
+ /**
171
+ * Move a view with the keyboard on the native side, as the bar is moved: content above the bar
172
+ * that should rise with it. Null when native does not move the bar. See `keyboardLift`.
173
+ */
174
+ lift(view, shift = null) {
175
+ if (!this.lifts)
176
+ return null;
177
+ const source = this.controllerView();
178
+ const range = this.liftRange();
179
+ return this.engine.driveByEvent(view, source, KEYBOARD_FEED, 'translateY', range, [], shift);
180
+ }
181
+ liftBar() {
182
+ let drive = null;
183
+ afterRenderEffect(() => {
184
+ const range = this.liftRange();
185
+ untracked(() => {
186
+ if (drive)
187
+ return drive.update(range);
188
+ const bar = this.barView();
189
+ if (bar)
190
+ drive = this.lift(bar);
191
+ });
192
+ });
193
+ inject(DestroyRef).onDestroy(() => drive?.stop());
194
+ }
195
+ /** The controller's view, the one whose events carry the keyboard. */
196
+ controllerView() {
197
+ this.source ??= this.node.children.find((child) => child.kind === 'element') ?? this.node;
198
+ return this.source;
199
+ }
200
+ /** The view holding the content, the last of the dock's own. */
201
+ barView() {
202
+ return this.node.children.filter((child) => child.kind === 'element').at(-1);
203
+ }
204
+ onKeyboardMove(event) {
205
+ const height = event.nativeEvent?.height;
206
+ if (height !== undefined)
207
+ this.controlled.set(height);
208
+ }
209
+ onBarLayout(event) {
210
+ const height = event.nativeEvent?.layout?.height;
211
+ if (height !== undefined)
212
+ this.barHeight.set(height);
213
+ }
214
+ onLayout() {
215
+ if (this.lifts)
216
+ return;
217
+ this.engine.measure(this.node, (frame) => this.bottomEdge.set(frame.y + frame.height));
218
+ }
219
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardDock, deps: [], target: i0.ɵɵFactoryTarget.Component });
220
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: KeyboardDock, isStandalone: true, selector: "keyboard-dock", inputs: { backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "layout": "onLayout()" }, properties: { "style.paddingBottom": "padding()", "style.backgroundColor": "backgroundColor()" } }, queries: [{ propertyName: "field", first: true, predicate: TextInput, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "fieldInput", first: true, predicate: TextInput, descendants: true, isSignal: true }], exportAs: ["keyboardDock"], usesInheritance: true, ngImport: i0, template: `
221
+ <ng-template #content><ng-content /></ng-template>
222
+ @if (lifts) {
223
+ <keyboard-controller-view
224
+ [style]="outOfFlow"
225
+ (keyboardMoveStart)="onKeyboardMove($event)"
226
+ (keyboardMoveEnd)="onKeyboardMove($event)"
227
+ />
228
+ <keyboard-gesture-area
229
+ [style]="outOfFlow"
230
+ [offset]="barHeight()"
231
+ [textInputNativeID]="inputID()"
232
+ />
233
+ <view
234
+ collapsable="false"
235
+ [style.backgroundColor]="backgroundColor()"
236
+ (layout)="onBarLayout($event)"
237
+ ><ng-container [templateSlot]="content" [templateSlotContext]="none"
238
+ /></view>
239
+ } @else {
240
+ <ng-container [templateSlot]="content" [templateSlotContext]="none" />
241
+ }
242
+ `, isInline: true, dependencies: [{ kind: "directive", type: KeyboardControllerView, selector: "keyboard-controller-view" }, { kind: "directive", type: KeyboardGestureArea, selector: "keyboard-gesture-area", inputs: ["offset", "textInputNativeID"] }, { kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: View, selector: "view" }] });
243
+ }
244
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardDock, decorators: [{
245
+ type: Component,
246
+ args: [{
247
+ selector: 'keyboard-dock',
248
+ exportAs: 'keyboardDock',
249
+ imports: [KeyboardControllerView, KeyboardGestureArea, TemplateSlot, View],
250
+ // One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would
251
+ // be given the content at compile time, whichever branch renders.
252
+ template: `
253
+ <ng-template #content><ng-content /></ng-template>
254
+ @if (lifts) {
255
+ <keyboard-controller-view
256
+ [style]="outOfFlow"
257
+ (keyboardMoveStart)="onKeyboardMove($event)"
258
+ (keyboardMoveEnd)="onKeyboardMove($event)"
259
+ />
260
+ <keyboard-gesture-area
261
+ [style]="outOfFlow"
262
+ [offset]="barHeight()"
263
+ [textInputNativeID]="inputID()"
264
+ />
265
+ <view
266
+ collapsable="false"
267
+ [style.backgroundColor]="backgroundColor()"
268
+ (layout)="onBarLayout($event)"
269
+ ><ng-container [templateSlot]="content" [templateSlotContext]="none"
270
+ /></view>
271
+ } @else {
272
+ <ng-container [templateSlot]="content" [templateSlotContext]="none" />
273
+ }
274
+ `,
275
+ host: {
276
+ '[style.paddingBottom]': 'padding()',
277
+ '[style.backgroundColor]': 'backgroundColor()',
278
+ '(layout)': 'onLayout()',
279
+ },
280
+ }]
281
+ }], ctorParameters: () => [], propDecorators: { backgroundColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "backgroundColor", required: false }] }], field: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TextInput), { ...{ descendants: true, read: ElementRef }, isSignal: true }] }], fieldInput: [{ type: i0.ContentChild, args: [i0.forwardRef(() => TextInput), { ...{ descendants: true }, isSignal: true }] }] } });
282
+ let docks = 0;
283
+ /**
284
+ * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]="dock">` around a chat's
285
+ * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is
286
+ * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than
287
+ * drawn over what is above. Does nothing where native does not move the bar.
288
+ *
289
+ * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved
290
+ * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view
291
+ * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,
292
+ * and a transform does not change what it thinks.
293
+ */
294
+ export class KeyboardLift {
295
+ dock = input.required({ ...(ngDevMode ? { debugName: "dock" } : /* istanbul ignore next */ {}), alias: 'keyboardLift' });
296
+ padding = computed(() => {
297
+ const covered = this.dock().covered();
298
+ return covered > 0 ? covered : undefined;
299
+ }, /* @ts-ignore */
300
+ ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
301
+ constructor() {
302
+ const view = inject(ElementRef).nativeElement;
303
+ let drive = null;
304
+ afterRenderEffect(() => {
305
+ const dock = this.dock();
306
+ const range = dock.liftRange();
307
+ const covered = dock.covered();
308
+ untracked(() => {
309
+ if (!drive)
310
+ drive = dock.lift(view, covered);
311
+ drive?.update(range);
312
+ drive?.shift(covered);
313
+ });
314
+ });
315
+ inject(DestroyRef).onDestroy(() => drive?.stop());
316
+ }
317
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardLift, deps: [], target: i0.ɵɵFactoryTarget.Directive });
318
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.0", type: KeyboardLift, isStandalone: true, selector: "[keyboardLift]", inputs: { dock: { classPropertyName: "dock", publicName: "keyboardLift", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "style.paddingBottom": "padding()" } }, ngImport: i0 });
319
+ }
320
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardLift, decorators: [{
321
+ type: Directive,
322
+ args: [{
323
+ selector: '[keyboardLift]',
324
+ host: { '[style.paddingBottom]': 'padding()' },
325
+ }]
326
+ }], ctorParameters: () => [], propDecorators: { dock: [{ type: i0.Input, args: [{ isSignal: true, alias: "keyboardLift", required: true }] }] } });
327
+ //# sourceMappingURL=keyboard-dock.js.map