@ng-native/components 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animated-style.js +1 -1
- package/dist/animated-style.js.map +1 -1
- package/dist/content-container.js +1 -1
- package/dist/content-container.js.map +1 -1
- package/dist/gradient-text.js +1 -1
- package/dist/gradient-text.js.map +1 -1
- package/dist/keyboard-avoiding-view.d.ts +5 -1
- package/dist/keyboard-avoiding-view.js +13 -8
- package/dist/keyboard-avoiding-view.js.map +1 -1
- package/dist/keyboard-dock.d.ts +38 -6
- package/dist/keyboard-dock.js +85 -32
- package/dist/keyboard-dock.js.map +1 -1
- package/dist/markdown.d.ts +29 -4
- package/dist/markdown.js +72 -18
- package/dist/markdown.js.map +1 -1
- package/dist/sticky-headers.js +1 -1
- package/dist/sticky-headers.js.map +1 -1
- package/dist/view-base.js +1 -1
- package/dist/view-base.js.map +1 -1
- package/dist/virtual-list.d.ts +13 -0
- package/dist/virtual-list.js +23 -3
- package/dist/virtual-list.js.map +1 -1
- package/package.json +3 -3
package/dist/animated-style.js
CHANGED
|
@@ -32,7 +32,7 @@ export class AnimatedStyleBase {
|
|
|
32
32
|
node = inject(ElementRef).nativeElement;
|
|
33
33
|
// The shared host seam, read off the node as every primitive reads it, so a browser host serves
|
|
34
34
|
// this as well as Fabric does. Only the react tag is Fabric's own, and `connect()` asks for it.
|
|
35
|
-
engine = this.node.
|
|
35
|
+
engine = this.node.engine ?? inject(HostEngine);
|
|
36
36
|
animation = inject(ANIMATION);
|
|
37
37
|
/** A style object whose values may be animated nodes, plain numbers, or a mix. */
|
|
38
38
|
animatedStyle = input.required(/* @ts-ignore */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animated-style.js","sourceRoot":"","sources":["../src/animated-style.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EACL,SAAS,EACT,UAAU,EACV,eAAe,EACf,MAAM,EACN,MAAM,EACN,KAAK,EACL,SAAS,GACV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAkC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAE,SAAS,EAA4B,MAAM,gBAAgB,CAAC;;AAErE;;;;;GAKG;AAEH,MAAM,OAAO,iBAAiB;IACX,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;IACrE,gGAAgG;IAChG,gGAAgG;IAC/E,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"animated-style.js","sourceRoot":"","sources":["../src/animated-style.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Binds a style holding animated values to the element it sits on: the Angular equivalent of\n * React Native's `createAnimatedComponent`.\n *\n * ```html\n * <view [animatedStyle]=\"{ opacity: fade, transform: [{ translateY: slide }] }\">\n * ```\n *\n * Two drivers, and the difference is where the work happens. With `useNativeDriver: true` the\n * whole animation runs on the UI thread: this hands the graph the element's react tag once and\n * then does nothing per frame, so the animation keeps its frame rate with the JavaScript thread\n * blocked. Without it, every frame calls back here and is written to the node. A browser has only\n * the second kind: `animations-web.ts` supplies a graph that is JavaScript-driven throughout, and\n * `useNativeDriver` there changes nothing.\n *\n * A JavaScript frame commits outside change detection, one of the two exceptions to committing at\n * most once per change-detection pass (the other is a CSS transition). It has to: the graph advances on its own schedule, and waiting for the next change\n * detection pass would drop the frame. The commit is incremental like any other, and touches\n * only the nodes the frame changed.\n */\nimport {\n Directive,\n ElementRef,\n afterNextRender,\n effect,\n inject,\n input,\n untracked,\n} from '@angular/core';\nimport { Engine, HostEngine, type EngineNode, type HostNode } from '@ng-native/fabric';\nimport { ANIMATION, type AnimatedPropsHandle } from './animation.ts';\n\n/**\n * The behaviour, with the graph left as a dependency so it can be driven by a fake. `AnimatedStyle`\n * in `../animations.ts` is the one apps import: same selector, and it supplies the real graph\n * itself - React Native's on a device, `animated.ts` in a browser - so importing the directive is\n * the whole setup.\n */\n@Directive({ selector: '[animatedStyle]' })\nexport class AnimatedStyleBase {\n private readonly node = inject(ElementRef).nativeElement as HostNode;\n // The shared host seam, read off the node as every primitive reads it, so a browser host serves\n // this as well as Fabric does. Only the react tag is Fabric's own, and `connect()` asks for it.\n private readonly engine = this.node.engine ?? inject(HostEngine);\n private readonly animation = inject(ANIMATION);\n\n /** A style object whose values may be animated nodes, plain numbers, or a mix. */\n readonly animatedStyle = input.required<Record<string, unknown>>();\n\n private handle: AnimatedPropsHandle | null = null;\n private connected = false;\n\n constructor() {\n // A node has a react tag only once it has been committed, and a native-driven animation\n // never calls back into JavaScript, so there would otherwise be no moment to hand it over.\n afterNextRender(() => this.connect());\n\n effect((onCleanup) => {\n const style = this.animatedStyle();\n const handle = this.animation.props(style, () => untracked(() => this.frame()));\n this.handle = handle;\n this.connected = false;\n handle.attach();\n // The starting values, so the element is right before anything animates. This runs inside\n // change detection, so the commit it needs is the ordinary one.\n this.write(handle.read());\n\n onCleanup(() => {\n handle.detach();\n if (this.handle === handle) this.handle = null;\n });\n });\n }\n\n /**\n * One JavaScript-driven frame. Also the first chance to hand over the react tag: a node has\n * one only after it has been committed, and this runs after at least one commit has happened.\n */\n private frame(): void {\n const handle = this.handle;\n if (!handle) return;\n this.write(handle.read());\n this.engine.commit();\n }\n\n private write(values: Record<string, unknown>): void {\n const current = (this.node.props['style'] as Record<string, unknown> | undefined) ?? {};\n this.engine.setProp(this.node, 'style', { ...current, ...values });\n this.connect();\n }\n\n /**\n * Hand the graph the react tag, once there is one. Calling this before an animation starts is\n * the point: React Native connects the view now if the graph is already native, and remembers\n * it for the moment an animation with the native driver takes over.\n */\n private connect(): void {\n if (this.connected) return;\n // A browser has no native driver to hand a view to, so there is no tag to look for.\n if (!(this.engine instanceof Engine)) return;\n const tag = this.engine.tagOf(this.node as EngineNode);\n if (tag === null) return;\n this.handle?.connect(tag);\n this.connected = true;\n }\n}\n"]}
|
|
@@ -18,7 +18,7 @@ export class ContentContainer {
|
|
|
18
18
|
/** The app's classes for the view. */
|
|
19
19
|
classes = input(undefined, { ...(ngDevMode ? { debugName: "classes" } : /* istanbul ignore next */ {}), alias: 'contentContainerClass' });
|
|
20
20
|
node = inject(ElementRef).nativeElement;
|
|
21
|
-
engine = this.node.
|
|
21
|
+
engine = this.node.engine ?? inject(HostEngine);
|
|
22
22
|
ngOnChanges() {
|
|
23
23
|
this.engine.adoptScope(this.node, this.classes() ? this.owner() : null);
|
|
24
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-container.js","sourceRoot":"","sources":["../src/content-container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAkB,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,UAAU,EAAiB,MAAM,mBAAmB,CAAC;;AAE9D;;;;;;;;;;GAUG;AAKH,MAAM,OAAO,gBAAgB;IAC3B,6EAA6E;IACpE,KAAK,GAAG,KAAK,CAAC,QAAQ,4EAAa,KAAK,EAAE,oBAAoB,GAAG,CAAC;IAC3E,sCAAsC;IAC7B,OAAO,GAAG,KAAK,CAAS,SAAS,+EAAI,KAAK,EAAE,uBAAuB,GAAG,CAAC;IAE/D,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;IACpD,MAAM,GAAe,IAAI,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"content-container.js","sourceRoot":"","sources":["../src/content-container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAkB,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,UAAU,EAAiB,MAAM,mBAAmB,CAAC;;AAE9D;;;;;;;;;;GAUG;AAKH,MAAM,OAAO,gBAAgB;IAC3B,6EAA6E;IACpE,KAAK,GAAG,KAAK,CAAC,QAAQ,4EAAa,KAAK,EAAE,oBAAoB,GAAG,CAAC;IAC3E,sCAAsC;IAC7B,OAAO,GAAG,KAAK,CAAS,SAAS,+EAAI,KAAK,EAAE,uBAAuB,GAAG,CAAC;IAE/D,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;IACpD,MAAM,GAAe,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC;IAE7E,WAAW;QACT,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1E,CAAC;uGAXU,gBAAgB;2FAAhB,gBAAgB;;2FAAhB,gBAAgB;kBAJ5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,sBAAsB;oBAChC,IAAI,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE;iBACjC","sourcesContent":["import { Directive, ElementRef, type OnChanges, inject, input } from '@angular/core';\nimport { HostEngine, type HostNode } from '@ng-native/fabric';\n\n/**\n * The view a component makes to hold its children, styled by the app through\n * `contentContainerClass`: `<scroll-view>`'s content view, and the view `<keyboard-avoiding-view>`\n * moves.\n *\n * The view is written in the component's template, so on its own it matches only that\n * component's rules and the global sheet. The class is the app's, so while there is one the view\n * is matched against the rules of the template the component is written in, and against its own\n * again once the class is gone. Not before: a type selector in the app's styles would otherwise\n * reach a view that nobody there wrote.\n */\n@Directive({\n selector: '[contentContainerOf]',\n host: { '[class]': 'classes()' },\n})\nexport class ContentContainer implements OnChanges {\n /** The component the view belongs to, whose template the app wrote it in. */\n readonly owner = input.required<HostNode>({ alias: 'contentContainerOf' });\n /** The app's classes for the view. */\n readonly classes = input<string>(undefined, { alias: 'contentContainerClass' });\n\n private readonly node = inject(ElementRef).nativeElement as HostNode;\n private readonly engine: HostEngine = this.node.engine ?? inject(HostEngine);\n\n ngOnChanges(): void {\n this.engine.adoptScope(this.node, this.classes() ? this.owner() : null);\n }\n}\n"]}
|
package/dist/gradient-text.js
CHANGED
|
@@ -37,7 +37,7 @@ import { View } from "./view.js";
|
|
|
37
37
|
import { ViewBase } from "./view-base.js";
|
|
38
38
|
import * as i0 from "@angular/core";
|
|
39
39
|
/** The characters under a node, in order. */
|
|
40
|
-
const textOf = (node) => node.
|
|
40
|
+
const textOf = (node) => node.nodeKind === 'text' ? node.nodeText : node.childList.map(textOf).join('');
|
|
41
41
|
let registered = false;
|
|
42
42
|
/** Teach the engine the element, once, before its first commit. */
|
|
43
43
|
function registerGradientText() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gradient-text.js","sourceRoot":"","sources":["../src/gradient-text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,GAGV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAgC,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;;AAE1C,6CAA6C;AAC7C,MAAM,MAAM,GAAG,CAAC,IAAgB,EAAU,EAAE,CAC1C,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"gradient-text.js","sourceRoot":"","sources":["../src/gradient-text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Text filled with a gradient, or with any background: the background is drawn through the shapes\n * of the letters.\n *\n * ```html\n * <gradient-text class=\"brand\">Week</gradient-text>\n * ```\n *\n * ```css\n * .brand {\n * background-image: linear-gradient(to bottom, #437dfc, #4ad0ef);\n * background-clip: text;\n * color: transparent;\n * font-size: 34px;\n * font-weight: 700;\n * }\n * ```\n *\n * The rule is the one a web page writes. `background-clip: text` is what this element does, so a\n * rule that has it applies to `<gradient-text>` and to nothing else: on a `<text>` it would leave\n * transparent letters on a block of colour. The `color` is not used, since the letters are the\n * mask and are drawn in full.\n *\n * It is text like any other: it wraps, follows the device's text size, takes `<text>` spans\n * inside it with their own weight or style, and is sized by its letters. What a span cannot have\n * here is a colour of its own, because the fill is the one background behind all of it.\n *\n * The app must have `@react-native-masked-view/masked-view` installed, which is where the native\n * view comes from (`npx expo install @react-native-masked-view/masked-view`); none of its\n * JavaScript is imported. The view masks what is inside it by its first child, so the letters are\n * that child and the background is painted on a view behind them.\n */\nimport {\n Component,\n ElementRef,\n viewChild,\n type AfterContentChecked,\n type AfterViewInit,\n} from '@angular/core';\nimport { registerViewName, type Engine, type EngineNode } from '@ng-native/fabric';\nimport { Text } from './text.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/** The characters under a node, in order. */\nconst textOf = (node: EngineNode): string =>\n node.nodeKind === 'text' ? node.nodeText : node.childList.map(textOf).join('');\n\nlet registered = false;\n\n/** Teach the engine the element, once, before its first commit. */\nfunction registerGradientText(): void {\n if (registered) return;\n registered = true;\n registerViewName('gradient-text', 'RNCMaskedView');\n}\n\n@Component({\n selector: 'gradient-text',\n imports: [Text, View],\n template: `\n <text #letters class=\"letters\"><ng-content /></text>\n <view #fill class=\"fill\" pointerEvents=\"none\"></view>\n `,\n styles: `\n .letters {\n color: black;\n }\n .fill {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n `,\n})\nexport class GradientText extends ViewBase implements AfterViewInit, AfterContentChecked {\n private readonly fill = viewChild.required('fill', { read: ElementRef });\n private readonly letters = viewChild('letters', { read: ElementRef });\n /** The label last written from the letters, to write it again only when they change. */\n private spoken: string | undefined;\n\n constructor() {\n super();\n registerGradientText();\n // One element to a screen reader, as a text is. An input of the same name is written after.\n this.engine.setProp(this.node, 'accessible', true);\n this.engine.setProp(this.node, 'accessibilityRole', 'text');\n }\n\n ngAfterViewInit(): void {\n this.paintOnFill();\n this.labelFromLetters();\n }\n\n ngAfterContentChecked(): void {\n this.labelFromLetters();\n }\n\n /**\n * Say the letters. iOS takes the mask out of the view hierarchy, so there is no text under the\n * element for VoiceOver to read, and an element with nothing to say is not found at all. A\n * label the app gave is left as it is.\n */\n private labelFromLetters(): void {\n const letters = this.letters()?.nativeElement as EngineNode | undefined;\n const given = this.accessibilityLabel() ?? this.ariaLabel();\n if (!letters || given !== undefined) return;\n const label = textOf(letters).replace(/\\s+/g, ' ').trim();\n if (label === this.spoken) return;\n this.spoken = label;\n this.engine.setProp(this.node, 'accessibilityLabel', label || null);\n }\n\n /**\n * The host's background is the fill's to paint: the view masks its children and not itself.\n * Before the first commit, so the background is never drawn unmasked.\n */\n private paintOnFill(): void {\n const engine = this.engine as Partial<Pick<Engine, 'paintOn'>>;\n engine.paintOn?.(this.node as EngineNode, this.fill().nativeElement as EngineNode);\n }\n}\n"]}
|
|
@@ -23,7 +23,9 @@ export type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';
|
|
|
23
23
|
* The adjustment itself animates with the keyboard's own motion, the way RN's own
|
|
24
24
|
* KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next
|
|
25
25
|
* layout animation before the style change that moves this view, so the two run together rather
|
|
26
|
-
* than the padding, height or position jumping to its resting place in one step.
|
|
26
|
+
* than the padding, height or position jumping to its resting place in one step. Android animates
|
|
27
|
+
* no layout unless an app turns that on, and reports no duration, so there the view jumps; with
|
|
28
|
+
* `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.
|
|
27
29
|
*/
|
|
28
30
|
export declare class KeyboardAvoidingView extends ViewBase {
|
|
29
31
|
/**
|
|
@@ -45,6 +47,8 @@ export declare class KeyboardAvoidingView extends ViewBase {
|
|
|
45
47
|
* header's height; here the view is measured in the window, so a navigation bar needs none.
|
|
46
48
|
*/
|
|
47
49
|
readonly keyboardVerticalOffset: import("@angular/core").InputSignalWithTransform<number, unknown>;
|
|
50
|
+
private readonly keyboardService;
|
|
51
|
+
/** Where the keyboard is, at every frame of its motion where the platform says. */
|
|
48
52
|
private readonly keyboard;
|
|
49
53
|
private readonly layoutAnimation;
|
|
50
54
|
private readonly frame;
|
|
@@ -41,7 +41,9 @@ function layoutEasingOf(easing) {
|
|
|
41
41
|
* The adjustment itself animates with the keyboard's own motion, the way RN's own
|
|
42
42
|
* KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next
|
|
43
43
|
* layout animation before the style change that moves this view, so the two run together rather
|
|
44
|
-
* than the padding, height or position jumping to its resting place in one step.
|
|
44
|
+
* than the padding, height or position jumping to its resting place in one step. Android animates
|
|
45
|
+
* no layout unless an app turns that on, and reports no duration, so there the view jumps; with
|
|
46
|
+
* `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.
|
|
45
47
|
*/
|
|
46
48
|
export class KeyboardAvoidingView extends ViewBase {
|
|
47
49
|
/**
|
|
@@ -66,7 +68,9 @@ export class KeyboardAvoidingView extends ViewBase {
|
|
|
66
68
|
* header's height; here the view is measured in the window, so a navigation bar needs none.
|
|
67
69
|
*/
|
|
68
70
|
keyboardVerticalOffset = input(0, { ...(ngDevMode ? { debugName: "keyboardVerticalOffset" } : /* istanbul ignore next */ {}), transform: numberOr(0) });
|
|
69
|
-
|
|
71
|
+
keyboardService = inject(Keyboard);
|
|
72
|
+
/** Where the keyboard is, at every frame of its motion where the platform says. */
|
|
73
|
+
keyboard = this.keyboardService.position;
|
|
70
74
|
layoutAnimation = inject(LayoutAnimation);
|
|
71
75
|
frame = signal(null, /* @ts-ignore */
|
|
72
76
|
...(ngDevMode ? [{ debugName: "frame" }] : /* istanbul ignore next */ []));
|
|
@@ -116,13 +120,14 @@ export class KeyboardAvoidingView extends ViewBase {
|
|
|
116
120
|
...(ngDevMode ? [{ debugName: "positioned" }] : /* istanbul ignore next */ []));
|
|
117
121
|
constructor() {
|
|
118
122
|
super();
|
|
119
|
-
// A duration of zero is Android's own keyboard event, which
|
|
120
|
-
// nothing to configure alongside it.
|
|
121
|
-
// update to a view already on screen, not
|
|
122
|
-
// `LayoutAnimation.configureNext({duration, update})`
|
|
123
|
+
// A duration of zero is Android's own keyboard event, which says nothing of the motion; there
|
|
124
|
+
// is nothing to configure alongside it. Nor where the view follows the motion frame by frame.
|
|
125
|
+
// `appear`/`leave` are turned off because this is an update to a view already on screen, not
|
|
126
|
+
// one appearing or leaving - RN's own bare `LayoutAnimation.configureNext({duration, update})`
|
|
127
|
+
// animates nothing else either.
|
|
123
128
|
effect(() => {
|
|
124
|
-
const { duration, easing } = this.
|
|
125
|
-
if (!this.enabled() || !duration)
|
|
129
|
+
const { duration, easing } = this.keyboardService.metrics();
|
|
130
|
+
if (!this.enabled() || !duration || this.keyboardService.followsMotion)
|
|
126
131
|
return;
|
|
127
132
|
void this.layoutAnimation.animate(() => { }, {
|
|
128
133
|
duration,
|
|
@@ -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.\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 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"]}
|
|
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"]}
|
package/dist/keyboard-dock.d.ts
CHANGED
|
@@ -18,6 +18,15 @@ export declare class KeyboardControllerView {
|
|
|
18
18
|
static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardControllerView, never>;
|
|
19
19
|
static ɵdir: i0.ɵɵDirectiveDeclaration<KeyboardControllerView, "keyboard-controller-view", never, {}, {}, never, never, true, never>;
|
|
20
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at
|
|
23
|
+
* every frame of its motion. See `KeyboardDock`.
|
|
24
|
+
*/
|
|
25
|
+
export declare class KeyboardMotionView {
|
|
26
|
+
constructor();
|
|
27
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardMotionView, never>;
|
|
28
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<KeyboardMotionView, "keyboard-motion-view", never, {}, {}, never, never, true, never>;
|
|
29
|
+
}
|
|
21
30
|
/**
|
|
22
31
|
* react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with
|
|
23
32
|
* `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.
|
|
@@ -42,9 +51,17 @@ export declare class KeyboardGestureArea {
|
|
|
42
51
|
* the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves
|
|
43
52
|
* content above with it.
|
|
44
53
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
54
|
+
* On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by
|
|
55
|
+
* a view of that package's that reports how far the keyboard covers the dock at every frame. It is
|
|
56
|
+
* measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet
|
|
57
|
+
* react-native-screens raises with the keyboard, rises only as far as it still must. Android does
|
|
58
|
+
* not use react-native-keyboard-controller: its view adds the navigation bar as margins around the
|
|
59
|
+
* whole app's content, which an app drawn edge to edge does not want.
|
|
60
|
+
*
|
|
61
|
+
* Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the
|
|
62
|
+
* keyboard starts to move; on Android it jumps into place then, rather than moving with the
|
|
63
|
+
* keyboard. A window the system resized for the keyboard already puts it clear, and then it is not
|
|
64
|
+
* padded at all.
|
|
48
65
|
*
|
|
49
66
|
* `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
|
|
50
67
|
* place, for content above that keeps clear of it with padding or an inset rather than moving.
|
|
@@ -60,7 +77,19 @@ export declare class KeyboardDock extends ViewBase {
|
|
|
60
77
|
width: number;
|
|
61
78
|
height: number;
|
|
62
79
|
};
|
|
63
|
-
|
|
80
|
+
protected readonly overDock: {
|
|
81
|
+
position: string;
|
|
82
|
+
top: number;
|
|
83
|
+
bottom: number;
|
|
84
|
+
left: number;
|
|
85
|
+
width: number;
|
|
86
|
+
};
|
|
87
|
+
/** On Android, the view that reports the keyboard's motion, where the build has one. */
|
|
88
|
+
protected readonly motion: import("@ng-native/device").KeyboardMotion | null;
|
|
89
|
+
/**
|
|
90
|
+
* Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:
|
|
91
|
+
* neither changes under an app.
|
|
92
|
+
*/
|
|
64
93
|
protected readonly lifts: boolean;
|
|
65
94
|
private readonly field;
|
|
66
95
|
private readonly fieldInput;
|
|
@@ -68,7 +97,10 @@ export declare class KeyboardDock extends ViewBase {
|
|
|
68
97
|
private readonly bottomEdge;
|
|
69
98
|
/** The bar's own height, measured, which the drag that lowers the keyboard starts above. */
|
|
70
99
|
protected readonly barHeight: import("@angular/core").WritableSignal<number>;
|
|
71
|
-
/**
|
|
100
|
+
/**
|
|
101
|
+
* The keyboard's height over the dock as native last reported it: the controller's keyboard
|
|
102
|
+
* height, excluding the drag area, or the motion view's cover of the dock.
|
|
103
|
+
*/
|
|
72
104
|
private readonly controlled;
|
|
73
105
|
/** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */
|
|
74
106
|
private readonly inset;
|
|
@@ -90,7 +122,7 @@ export declare class KeyboardDock extends ViewBase {
|
|
|
90
122
|
*/
|
|
91
123
|
lift(view: HostNode, shift?: number | null): ScrollDrive | null;
|
|
92
124
|
private liftBar;
|
|
93
|
-
/** The controller's view, the one whose events carry the keyboard. */
|
|
125
|
+
/** The controller's or the motion view, the one whose events carry the keyboard. */
|
|
94
126
|
private controllerView;
|
|
95
127
|
/** The view holding the content, the last of the dock's own. */
|
|
96
128
|
private barView;
|
package/dist/keyboard-dock.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
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';
|
|
2
|
+
import { KEYBOARD_MOTION, Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';
|
|
3
3
|
import { claimHost, nativePlatform, registerViewName, } from '@ng-native/fabric';
|
|
4
4
|
import { TemplateSlot } from "./template-slot.js";
|
|
5
5
|
import { TextInput } from "./text-input.js";
|
|
@@ -30,6 +30,8 @@ const KEYBOARD_FEED = {
|
|
|
30
30
|
const FAR = 10_000;
|
|
31
31
|
/** A native view the dock renders for itself, out of the way of the layout. */
|
|
32
32
|
const OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };
|
|
33
|
+
/** Over the dock, out of the way of the layout, so its bottom edge is the dock's. */
|
|
34
|
+
const OVER_DOCK = { position: 'absolute', top: 0, bottom: 0, left: 0, width: 0 };
|
|
33
35
|
/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */
|
|
34
36
|
export class KeyboardControllerView {
|
|
35
37
|
constructor() {
|
|
@@ -46,6 +48,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
|
|
|
46
48
|
host: { '[enabled]': 'true' },
|
|
47
49
|
}]
|
|
48
50
|
}], ctorParameters: () => [] });
|
|
51
|
+
/**
|
|
52
|
+
* The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at
|
|
53
|
+
* every frame of its motion. See `KeyboardDock`.
|
|
54
|
+
*/
|
|
55
|
+
export class KeyboardMotionView {
|
|
56
|
+
constructor() {
|
|
57
|
+
const motion = inject(KEYBOARD_MOTION);
|
|
58
|
+
if (motion)
|
|
59
|
+
registerViewName('keyboard-motion-view', motion.viewName);
|
|
60
|
+
claimHost(inject(ElementRef).nativeElement);
|
|
61
|
+
}
|
|
62
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardMotionView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
63
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.2.0", type: KeyboardMotionView, isStandalone: true, selector: "keyboard-motion-view", ngImport: i0 });
|
|
64
|
+
}
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardMotionView, decorators: [{
|
|
66
|
+
type: Directive,
|
|
67
|
+
args: [{ selector: 'keyboard-motion-view' }]
|
|
68
|
+
}], ctorParameters: () => [] });
|
|
49
69
|
/**
|
|
50
70
|
* react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with
|
|
51
71
|
* `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.
|
|
@@ -82,9 +102,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
|
|
|
82
102
|
* the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves
|
|
83
103
|
* content above with it.
|
|
84
104
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
105
|
+
* On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by
|
|
106
|
+
* a view of that package's that reports how far the keyboard covers the dock at every frame. It is
|
|
107
|
+
* measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet
|
|
108
|
+
* react-native-screens raises with the keyboard, rises only as far as it still must. Android does
|
|
109
|
+
* not use react-native-keyboard-controller: its view adds the navigation bar as margins around the
|
|
110
|
+
* whole app's content, which an app drawn edge to edge does not want.
|
|
111
|
+
*
|
|
112
|
+
* Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the
|
|
113
|
+
* keyboard starts to move; on Android it jumps into place then, rather than moving with the
|
|
114
|
+
* keyboard. A window the system resized for the keyboard already puts it clear, and then it is not
|
|
115
|
+
* padded at all.
|
|
88
116
|
*
|
|
89
117
|
* `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
|
|
90
118
|
* place, for content above that keeps clear of it with padding or an inset rather than moving.
|
|
@@ -97,8 +125,14 @@ export class KeyboardDock extends ViewBase {
|
|
|
97
125
|
safeArea = inject(SafeArea);
|
|
98
126
|
none = {};
|
|
99
127
|
outOfFlow = OUT_OF_FLOW;
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
overDock = OVER_DOCK;
|
|
129
|
+
/** On Android, the view that reports the keyboard's motion, where the build has one. */
|
|
130
|
+
motion = nativePlatform() === 'android' ? inject(KEYBOARD_MOTION) : null;
|
|
131
|
+
/**
|
|
132
|
+
* Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:
|
|
133
|
+
* neither changes under an app.
|
|
134
|
+
*/
|
|
135
|
+
lifts = this.motion !== null || (nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER));
|
|
102
136
|
field = contentChild(TextInput, { ...(ngDevMode ? { debugName: "field" } : /* istanbul ignore next */ {}), descendants: true, read: ElementRef });
|
|
103
137
|
fieldInput = contentChild(TextInput, { ...(ngDevMode ? { debugName: "fieldInput" } : /* istanbul ignore next */ {}), descendants: true });
|
|
104
138
|
/** Where the dock's bottom edge is in the window, which the keyboard is compared to. */
|
|
@@ -107,7 +141,10 @@ export class KeyboardDock extends ViewBase {
|
|
|
107
141
|
/** The bar's own height, measured, which the drag that lowers the keyboard starts above. */
|
|
108
142
|
barHeight = signal(0, /* @ts-ignore */
|
|
109
143
|
...(ngDevMode ? [{ debugName: "barHeight" }] : /* istanbul ignore next */ []));
|
|
110
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* The keyboard's height over the dock as native last reported it: the controller's keyboard
|
|
146
|
+
* height, excluding the drag area, or the motion view's cover of the dock.
|
|
147
|
+
*/
|
|
111
148
|
controlled = signal(0, /* @ts-ignore */
|
|
112
149
|
...(ngDevMode ? [{ debugName: "controlled" }] : /* istanbul ignore next */ []));
|
|
113
150
|
/** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */
|
|
@@ -192,14 +229,14 @@ export class KeyboardDock extends ViewBase {
|
|
|
192
229
|
});
|
|
193
230
|
inject(DestroyRef).onDestroy(() => drive?.stop());
|
|
194
231
|
}
|
|
195
|
-
/** The controller's view, the one whose events carry the keyboard. */
|
|
232
|
+
/** The controller's or the motion view, the one whose events carry the keyboard. */
|
|
196
233
|
controllerView() {
|
|
197
|
-
this.source ??= this.node.
|
|
234
|
+
this.source ??= this.node.childList.find((child) => child.nodeKind === 'element') ?? this.node;
|
|
198
235
|
return this.source;
|
|
199
236
|
}
|
|
200
237
|
/** The view holding the content, the last of the dock's own. */
|
|
201
238
|
barView() {
|
|
202
|
-
return this.node.
|
|
239
|
+
return this.node.childList.filter((child) => child.nodeKind === 'element').at(-1);
|
|
203
240
|
}
|
|
204
241
|
onKeyboardMove(event) {
|
|
205
242
|
const height = event.nativeEvent?.height;
|
|
@@ -220,16 +257,24 @@ export class KeyboardDock extends ViewBase {
|
|
|
220
257
|
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
258
|
<ng-template #content><ng-content /></ng-template>
|
|
222
259
|
@if (lifts) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
260
|
+
@if (motion) {
|
|
261
|
+
<keyboard-motion-view
|
|
262
|
+
[style]="overDock"
|
|
263
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
264
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
265
|
+
/>
|
|
266
|
+
} @else {
|
|
267
|
+
<keyboard-controller-view
|
|
268
|
+
[style]="outOfFlow"
|
|
269
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
270
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
271
|
+
/>
|
|
272
|
+
<keyboard-gesture-area
|
|
273
|
+
[style]="outOfFlow"
|
|
274
|
+
[offset]="barHeight()"
|
|
275
|
+
[textInputNativeID]="inputID()"
|
|
276
|
+
/>
|
|
277
|
+
}
|
|
233
278
|
<view
|
|
234
279
|
collapsable="false"
|
|
235
280
|
[style.backgroundColor]="backgroundColor()"
|
|
@@ -239,29 +284,37 @@ export class KeyboardDock extends ViewBase {
|
|
|
239
284
|
} @else {
|
|
240
285
|
<ng-container [templateSlot]="content" [templateSlotContext]="none" />
|
|
241
286
|
}
|
|
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" }] });
|
|
287
|
+
`, 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: KeyboardMotionView, selector: "keyboard-motion-view" }, { kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: View, selector: "view" }] });
|
|
243
288
|
}
|
|
244
289
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardDock, decorators: [{
|
|
245
290
|
type: Component,
|
|
246
291
|
args: [{
|
|
247
292
|
selector: 'keyboard-dock',
|
|
248
293
|
exportAs: 'keyboardDock',
|
|
249
|
-
imports: [KeyboardControllerView, KeyboardGestureArea, TemplateSlot, View],
|
|
294
|
+
imports: [KeyboardControllerView, KeyboardGestureArea, KeyboardMotionView, TemplateSlot, View],
|
|
250
295
|
// One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would
|
|
251
296
|
// be given the content at compile time, whichever branch renders.
|
|
252
297
|
template: `
|
|
253
298
|
<ng-template #content><ng-content /></ng-template>
|
|
254
299
|
@if (lifts) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
300
|
+
@if (motion) {
|
|
301
|
+
<keyboard-motion-view
|
|
302
|
+
[style]="overDock"
|
|
303
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
304
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
305
|
+
/>
|
|
306
|
+
} @else {
|
|
307
|
+
<keyboard-controller-view
|
|
308
|
+
[style]="outOfFlow"
|
|
309
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
310
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
311
|
+
/>
|
|
312
|
+
<keyboard-gesture-area
|
|
313
|
+
[style]="outOfFlow"
|
|
314
|
+
[offset]="barHeight()"
|
|
315
|
+
[textInputNativeID]="inputID()"
|
|
316
|
+
/>
|
|
317
|
+
}
|
|
265
318
|
<view
|
|
266
319
|
collapsable="false"
|
|
267
320
|
[style.backgroundColor]="backgroundColor()"
|