@ng-native/components 0.17.0 → 0.18.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/gestures.js CHANGED
@@ -68,7 +68,11 @@ export {} from "./gesture-backend.js";
68
68
  */
69
69
  const { attachHandlers } = require('react-native-gesture-handler/src/handlers/gestures/GestureDetector/attachHandlers.ts');
70
70
  const { dropHandlers } = require('react-native-gesture-handler/src/handlers/gestures/GestureDetector/dropHandlers.ts');
71
- const { maybeInitializeFabric } = require('react-native-gesture-handler/src/init.ts');
71
+ /**
72
+ * Gesture handler 2 installs its JSI bindings here, once Fabric is up. Gesture handler 3 (Expo SDK
73
+ * 58) has no such step: `attachHandlers` loads its Reanimated wrapper, which installs them.
74
+ */
75
+ const { maybeInitializeFabric = () => { } } = require('react-native-gesture-handler/src/init.ts');
72
76
  const { GestureStateManager } = require('react-native-gesture-handler/src/handlers/gestures/gestureStateManager.ts');
73
77
  /**
74
78
  * The same call Reanimated's own `WorkletEventHandler` makes: one registration per event name per
@@ -147,8 +151,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
147
151
  export class GestureRoot {
148
152
  constructor() {
149
153
  const android = nativePlatform() === 'android';
154
+ // Gesture handler 3's Android root finds its registry by the native module's id, which the
155
+ // module puts on the runtime, and throws without one. Gesture handler 2 has neither.
156
+ const moduleId = globalThis._RNGH_MODULE_ID;
150
157
  registerViewName('gesture-root', android ? 'RNGestureHandlerRootView' : 'RCTView', {
151
158
  flex: 1,
159
+ ...(android && moduleId !== undefined && { moduleId }),
152
160
  });
153
161
  claimHost(inject(ElementRef).nativeElement);
154
162
  // The library does this in its root view's render: it installs the JSI bindings the new
@@ -1 +1 @@
1
- {"version":3,"file":"gestures.js","sourceRoot":"","sources":["../src/gestures.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * react-native-gesture-handler, without React: a native gesture attached to one of our nodes.\n *\n * ```ts\n * import { Gesture } from 'react-native-gesture-handler';\n * import { NativeGesture } from '@ng-native/components/gestures';\n *\n * protected readonly pan = Gesture.Pan().onUpdate((event) => {\n * 'worklet';\n * x.value = event.translationX;\n * });\n * ```\n * ```html\n * <gesture-root>\n * <view [gesture]=\"pan\" [workletStyle]=\"slide\"></view>\n * </gesture-root>\n * ```\n *\n * `GestureDetector` is a React component whose job is to hold a ref, turn it into a view tag, and\n * hand that tag to the native module. Everything under it is a plain function of that tag:\n * `attachHandlers` and `dropHandlers` import no React at runtime, so they are used here as they\n * are, and the gesture objects themselves - `Gesture.Pan()`, the compositions, the config - are\n * the library's own public API and come across untouched.\n *\n * What is written out is the part `useAnimatedGesture` does with hooks: a worklet that turns the\n * raw stream of events into the callbacks a gesture declares. Reanimated's `registerEventHandler`\n * is the same call their `WorkletEventHandler` makes, once per event name per view tag.\n *\n * A gesture whose callbacks are not worklets needs none of that: the library attaches it as a\n * JavaScript-function gesture and its own event receiver, started when the package is first\n * imported, calls them on the JavaScript thread.\n *\n * **A gesture callback must not read `this`, on either thread.** The Babel plugin rewrites one\n * into a worklet whether or not it will run on the UI thread, and a rewritten callback has no\n * `this`; `runOnJS(true)` decides where it runs, not what it may close over. Read what it needs\n * through a local, as a worklet style does.\n *\n * **The internals are reached through `src/`, and that is load-bearing.** Both libraries point\n * their `react-native` package field at their TypeScript source, and Metro prefers that field, so\n * `react-native-gesture-handler` in an app resolves to `src/index.ts`. Importing the built `lib/`\n * copy of a file instead gives a second, parallel copy of the library with its own module state:\n * handlers registered in one copy's registry, the event receiver listening in the other's, and\n * every JavaScript gesture silently doing nothing.\n *\n * Deliberately outside the barrel: everything here reaches React Native's Flow source, which Node\n * cannot parse, and the rest of the package has to stay loadable in the test suite.\n */\nimport { Component, Directive, ElementRef, inject } from '@angular/core';\nimport { claimHost, nativePlatform, registerViewName, type HostNode } from '@ng-native/fabric';\nimport {\n gestureDispatcher,\n type GestureCallbacks,\n type GestureStateManager,\n} from './gesture-dispatch.ts';\nimport { GESTURES, type GestureBackend } from './gesture-backend.ts';\nimport { NativeGestureBase } from './native-gesture.ts';\n\n// For the side effect: evaluating the package's index is what starts its event receiver, which\n// is what delivers a gesture whose callbacks are ordinary functions. An app importing `Gesture`\n// does this too; this file does not depend on that. A `require`, so the published types do not\n// name a library a web app may not have installed.\nrequire('react-native-gesture-handler');\n\nexport { type GestureSpec, type GestureTarget } from './gesture-backend.ts';\n\n/**\n * The internals, required rather than imported, and each with a literal path because Metro builds\n * its graph from those.\n *\n * Not for laziness: a static import would put these TypeScript sources into our own type-checking\n * program, where they fail against settings of ours they were never written for\n * (`verbatimModuleSyntax`, `erasableSyntaxOnly`). `require` asks Metro for the module and leaves\n * the compiler out of it, which is the same trade `@ng-native/device` makes for React Native.\n */\nconst { attachHandlers } =\n require('react-native-gesture-handler/src/handlers/gestures/GestureDetector/attachHandlers.ts') as {\n attachHandlers(config: {\n preparedGesture: object;\n gestureConfig: object;\n gesturesToAttach: readonly object[];\n viewTag: number;\n webEventHandlersRef: object;\n }): void;\n };\n\nconst { dropHandlers } =\n require('react-native-gesture-handler/src/handlers/gestures/GestureDetector/dropHandlers.ts') as {\n dropHandlers(prepared: object): void;\n };\n\nconst { maybeInitializeFabric } = require('react-native-gesture-handler/src/init.ts') as {\n maybeInitializeFabric(): void;\n};\n\nconst { GestureStateManager } =\n require('react-native-gesture-handler/src/handlers/gestures/gestureStateManager.ts') as {\n GestureStateManager: { create(handlerTag: number): GestureStateManager };\n };\n\n/**\n * The same call Reanimated's own `WorkletEventHandler` makes: one registration per event name per\n * view tag, which is all a gesture needs to reach the UI runtime. Not public either.\n */\nconst { registerEventHandler, unregisterEventHandler } =\n require('react-native-reanimated/src/core.ts') as {\n registerEventHandler(\n worklet: (event: never) => void,\n eventName: string,\n emitterReactTag: number,\n ): number;\n unregisterEventHandler(id: number): void;\n };\n\n/** The gesture object, as far as this file needs to know it. */\ninterface Attachable {\n initialize(): void;\n prepare(): void;\n toGestureArray(): { handlers: GestureCallbacks; shouldUseReanimated: boolean }[];\n}\n\nconst EVENT_NAMES = ['onGestureHandlerStateChange', 'onGestureHandlerEvent'];\n\n/**\n * A gesture's own state controller, for the touch callbacks: a worklet that accepts or rejects\n * the gesture while it is still being recognised. Wrapped in a worklet of ours because everything\n * the dispatcher closes over is serialised onto the UI runtime with it.\n */\nconst createManager = (handlerTag: number): GestureStateManager => {\n 'worklet';\n return GestureStateManager.create(handlerTag);\n};\n\nconst backend: GestureBackend = {\n attach: (target, gesture) => {\n maybeInitializeFabric();\n const config = gesture as Attachable;\n const gesturesToAttach = config.toGestureArray();\n // The library's own state object. Only `isMounted` is read back by anything of ours - the\n // microtasks `attachHandlers` queues check it, so a directive torn down in the same tick as\n // it was created does not attach a handler nobody will drop.\n const prepared = {\n attachedGestures: [],\n animatedEventHandler: null,\n animatedHandlers: null,\n shouldUseReanimated: gesturesToAttach.some((g) => g.shouldUseReanimated),\n isMounted: true,\n };\n attachHandlers({\n preparedGesture: prepared,\n gestureConfig: config,\n gesturesToAttach,\n viewTag: target.tag,\n // Web only, and there is no web here. The library reads `.current` off it on that platform.\n webEventHandlersRef: { current: {} },\n });\n\n // One dispatcher for both event names, as the library's own event handler is: a gesture's\n // state changes and its updates arrive on different names and share the same state.\n const animated = gesturesToAttach.filter((g) => g.shouldUseReanimated);\n const dispatch = gestureDispatcher(\n animated.map((g) => g.handlers),\n createManager,\n );\n const registrations = animated.length\n ? EVENT_NAMES.map((name) => registerEventHandler(dispatch, name, target.tag))\n : [];\n\n return () => {\n prepared.isMounted = false;\n for (const id of registrations) unregisterEventHandler(id);\n dropHandlers(prepared);\n };\n },\n};\n\n@Directive({\n selector: '[gesture]',\n providers: [{ provide: GESTURES, useValue: backend }],\n})\nexport class NativeGesture extends NativeGestureBase {}\n\n/**\n * The root the gestures are recognised inside. Wrap the app in one, as the library's own\n * `GestureHandlerRootView` is wrapped.\n *\n * **It is a different view on each platform, and that is the library's own split.**\n * `RNGestureHandlerRootView` is an Android view group that intercepts touches before the rest of\n * the tree sees them; on iOS the recognisers hang off the target view itself, the component\n * provider returns nil, and their own root view renders a plain `View`. Registering the native\n * name on both would render an unimplemented component on iOS.\n */\n@Component({ selector: 'gesture-root', template: '<ng-content />' })\nexport class GestureRoot {\n constructor() {\n const android = nativePlatform() === 'android';\n registerViewName('gesture-root', android ? 'RNGestureHandlerRootView' : 'RCTView', {\n flex: 1,\n });\n claimHost(inject(ElementRef).nativeElement as HostNode);\n // The library does this in its root view's render: it installs the JSI bindings the new\n // architecture needs, and it can only happen once Fabric is there to install them into.\n maybeInitializeFabric();\n }\n}\n"]}
1
+ {"version":3,"file":"gestures.js","sourceRoot":"","sources":["../src/gestures.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * react-native-gesture-handler, without React: a native gesture attached to one of our nodes.\n *\n * ```ts\n * import { Gesture } from 'react-native-gesture-handler';\n * import { NativeGesture } from '@ng-native/components/gestures';\n *\n * protected readonly pan = Gesture.Pan().onUpdate((event) => {\n * 'worklet';\n * x.value = event.translationX;\n * });\n * ```\n * ```html\n * <gesture-root>\n * <view [gesture]=\"pan\" [workletStyle]=\"slide\"></view>\n * </gesture-root>\n * ```\n *\n * `GestureDetector` is a React component whose job is to hold a ref, turn it into a view tag, and\n * hand that tag to the native module. Everything under it is a plain function of that tag:\n * `attachHandlers` and `dropHandlers` import no React at runtime, so they are used here as they\n * are, and the gesture objects themselves - `Gesture.Pan()`, the compositions, the config - are\n * the library's own public API and come across untouched.\n *\n * What is written out is the part `useAnimatedGesture` does with hooks: a worklet that turns the\n * raw stream of events into the callbacks a gesture declares. Reanimated's `registerEventHandler`\n * is the same call their `WorkletEventHandler` makes, once per event name per view tag.\n *\n * A gesture whose callbacks are not worklets needs none of that: the library attaches it as a\n * JavaScript-function gesture and its own event receiver, started when the package is first\n * imported, calls them on the JavaScript thread.\n *\n * **A gesture callback must not read `this`, on either thread.** The Babel plugin rewrites one\n * into a worklet whether or not it will run on the UI thread, and a rewritten callback has no\n * `this`; `runOnJS(true)` decides where it runs, not what it may close over. Read what it needs\n * through a local, as a worklet style does.\n *\n * **The internals are reached through `src/`, and that is load-bearing.** Both libraries point\n * their `react-native` package field at their TypeScript source, and Metro prefers that field, so\n * `react-native-gesture-handler` in an app resolves to `src/index.ts`. Importing the built `lib/`\n * copy of a file instead gives a second, parallel copy of the library with its own module state:\n * handlers registered in one copy's registry, the event receiver listening in the other's, and\n * every JavaScript gesture silently doing nothing.\n *\n * Deliberately outside the barrel: everything here reaches React Native's Flow source, which Node\n * cannot parse, and the rest of the package has to stay loadable in the test suite.\n */\nimport { Component, Directive, ElementRef, inject } from '@angular/core';\nimport { claimHost, nativePlatform, registerViewName, type HostNode } from '@ng-native/fabric';\nimport {\n gestureDispatcher,\n type GestureCallbacks,\n type GestureStateManager,\n} from './gesture-dispatch.ts';\nimport { GESTURES, type GestureBackend } from './gesture-backend.ts';\nimport { NativeGestureBase } from './native-gesture.ts';\n\n// For the side effect: evaluating the package's index is what starts its event receiver, which\n// is what delivers a gesture whose callbacks are ordinary functions. An app importing `Gesture`\n// does this too; this file does not depend on that. A `require`, so the published types do not\n// name a library a web app may not have installed.\nrequire('react-native-gesture-handler');\n\nexport { type GestureSpec, type GestureTarget } from './gesture-backend.ts';\n\n/**\n * The internals, required rather than imported, and each with a literal path because Metro builds\n * its graph from those.\n *\n * Not for laziness: a static import would put these TypeScript sources into our own type-checking\n * program, where they fail against settings of ours they were never written for\n * (`verbatimModuleSyntax`, `erasableSyntaxOnly`). `require` asks Metro for the module and leaves\n * the compiler out of it, which is the same trade `@ng-native/device` makes for React Native.\n */\nconst { attachHandlers } =\n require('react-native-gesture-handler/src/handlers/gestures/GestureDetector/attachHandlers.ts') as {\n attachHandlers(config: {\n preparedGesture: object;\n gestureConfig: object;\n gesturesToAttach: readonly object[];\n viewTag: number;\n webEventHandlersRef: object;\n }): void;\n };\n\nconst { dropHandlers } =\n require('react-native-gesture-handler/src/handlers/gestures/GestureDetector/dropHandlers.ts') as {\n dropHandlers(prepared: object): void;\n };\n\n/**\n * Gesture handler 2 installs its JSI bindings here, once Fabric is up. Gesture handler 3 (Expo SDK\n * 58) has no such step: `attachHandlers` loads its Reanimated wrapper, which installs them.\n */\nconst { maybeInitializeFabric = () => {} } =\n require('react-native-gesture-handler/src/init.ts') as {\n maybeInitializeFabric?(): void;\n };\n\nconst { GestureStateManager } =\n require('react-native-gesture-handler/src/handlers/gestures/gestureStateManager.ts') as {\n GestureStateManager: { create(handlerTag: number): GestureStateManager };\n };\n\n/**\n * The same call Reanimated's own `WorkletEventHandler` makes: one registration per event name per\n * view tag, which is all a gesture needs to reach the UI runtime. Not public either.\n */\nconst { registerEventHandler, unregisterEventHandler } =\n require('react-native-reanimated/src/core.ts') as {\n registerEventHandler(\n worklet: (event: never) => void,\n eventName: string,\n emitterReactTag: number,\n ): number;\n unregisterEventHandler(id: number): void;\n };\n\n/** The gesture object, as far as this file needs to know it. */\ninterface Attachable {\n initialize(): void;\n prepare(): void;\n toGestureArray(): { handlers: GestureCallbacks; shouldUseReanimated: boolean }[];\n}\n\nconst EVENT_NAMES = ['onGestureHandlerStateChange', 'onGestureHandlerEvent'];\n\n/**\n * A gesture's own state controller, for the touch callbacks: a worklet that accepts or rejects\n * the gesture while it is still being recognised. Wrapped in a worklet of ours because everything\n * the dispatcher closes over is serialised onto the UI runtime with it.\n */\nconst createManager = (handlerTag: number): GestureStateManager => {\n 'worklet';\n return GestureStateManager.create(handlerTag);\n};\n\nconst backend: GestureBackend = {\n attach: (target, gesture) => {\n maybeInitializeFabric();\n const config = gesture as Attachable;\n const gesturesToAttach = config.toGestureArray();\n // The library's own state object. Only `isMounted` is read back by anything of ours - the\n // microtasks `attachHandlers` queues check it, so a directive torn down in the same tick as\n // it was created does not attach a handler nobody will drop.\n const prepared = {\n attachedGestures: [],\n animatedEventHandler: null,\n animatedHandlers: null,\n shouldUseReanimated: gesturesToAttach.some((g) => g.shouldUseReanimated),\n isMounted: true,\n };\n attachHandlers({\n preparedGesture: prepared,\n gestureConfig: config,\n gesturesToAttach,\n viewTag: target.tag,\n // Web only, and there is no web here. The library reads `.current` off it on that platform.\n webEventHandlersRef: { current: {} },\n });\n\n // One dispatcher for both event names, as the library's own event handler is: a gesture's\n // state changes and its updates arrive on different names and share the same state.\n const animated = gesturesToAttach.filter((g) => g.shouldUseReanimated);\n const dispatch = gestureDispatcher(\n animated.map((g) => g.handlers),\n createManager,\n );\n const registrations = animated.length\n ? EVENT_NAMES.map((name) => registerEventHandler(dispatch, name, target.tag))\n : [];\n\n return () => {\n prepared.isMounted = false;\n for (const id of registrations) unregisterEventHandler(id);\n dropHandlers(prepared);\n };\n },\n};\n\n@Directive({\n selector: '[gesture]',\n providers: [{ provide: GESTURES, useValue: backend }],\n})\nexport class NativeGesture extends NativeGestureBase {}\n\n/**\n * The root the gestures are recognised inside. Wrap the app in one, as the library's own\n * `GestureHandlerRootView` is wrapped.\n *\n * **It is a different view on each platform, and that is the library's own split.**\n * `RNGestureHandlerRootView` is an Android view group that intercepts touches before the rest of\n * the tree sees them; on iOS the recognisers hang off the target view itself, the component\n * provider returns nil, and their own root view renders a plain `View`. Registering the native\n * name on both would render an unimplemented component on iOS.\n */\n@Component({ selector: 'gesture-root', template: '<ng-content />' })\nexport class GestureRoot {\n constructor() {\n const android = nativePlatform() === 'android';\n // Gesture handler 3's Android root finds its registry by the native module's id, which the\n // module puts on the runtime, and throws without one. Gesture handler 2 has neither.\n const moduleId = (globalThis as { _RNGH_MODULE_ID?: number })._RNGH_MODULE_ID;\n registerViewName('gesture-root', android ? 'RNGestureHandlerRootView' : 'RCTView', {\n flex: 1,\n ...(android && moduleId !== undefined && { moduleId }),\n });\n claimHost(inject(ElementRef).nativeElement as HostNode);\n // The library does this in its root view's render: it installs the JSI bindings the new\n // architecture needs, and it can only happen once Fabric is there to install them into.\n maybeInitializeFabric();\n }\n}\n"]}
@@ -13,10 +13,14 @@ export type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';
13
13
  *
14
14
  * The overlap is the view's own frame against the keyboard's top edge, so a view that does not
15
15
  * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is
16
- * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in
17
- * the parent's coordinates, and on a screen under a navigation bar those start a bar's height
16
+ * measured in the window, and the keyboard's `screenY` brought into it: `(layout)` reports the top
17
+ * in the parent's coordinates, and on a screen under a navigation bar those start a bar's height
18
18
  * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by
19
- * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the
19
+ * exactly that much, its bottom edge still behind the keyboard. On Android, when React Native's
20
+ * edge-to-edge flag is off (Expo Go leaves it off), the window starts below the status bar while
21
+ * `screenY` counts from the top of the screen, so the keyboard's top is taken `Keyboard.windowTop`
22
+ * higher in the window, or the view comes up short by the status bar; when the flag is on,
23
+ * `Keyboard.windowTop` is zero. The overlap never exceeds the
20
24
  * view's own height either: padding a view by more than it has pushes its content out of it.
21
25
  * When the keyboard source reports no `screenY`, the keyboard's height stands in.
22
26
  *
@@ -31,10 +31,14 @@ function layoutEasingOf(easing) {
31
31
  *
32
32
  * The overlap is the view's own frame against the keyboard's top edge, so a view that does not
33
33
  * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is
34
- * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in
35
- * the parent's coordinates, and on a screen under a navigation bar those start a bar's height
34
+ * measured in the window, and the keyboard's `screenY` brought into it: `(layout)` reports the top
35
+ * in the parent's coordinates, and on a screen under a navigation bar those start a bar's height
36
36
  * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by
37
- * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the
37
+ * exactly that much, its bottom edge still behind the keyboard. On Android, when React Native's
38
+ * edge-to-edge flag is off (Expo Go leaves it off), the window starts below the status bar while
39
+ * `screenY` counts from the top of the screen, so the keyboard's top is taken `Keyboard.windowTop`
40
+ * higher in the window, or the view comes up short by the status bar; when the flag is on,
41
+ * `Keyboard.windowTop` is zero. The overlap never exceeds the
38
42
  * view's own height either: padding a view by more than it has pushes its content out of it.
39
43
  * When the keyboard source reports no `screenY`, the keyboard's height stands in.
40
44
  *
@@ -91,7 +95,8 @@ export class KeyboardAvoidingView extends ViewBase {
91
95
  // had before, as RN adds its current adjustment back; otherwise the room it made reads as no
92
96
  // overlap and the height springs back under the keyboard.
93
97
  const extent = this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;
94
- const overlap = screenY === undefined ? height - offset : frame.y + extent - (screenY - offset);
98
+ const top = screenY === undefined ? undefined : screenY - this.keyboardService.windowTop;
99
+ const overlap = top === undefined ? height - offset : frame.y + extent - (top - offset);
95
100
  return Math.min(extent, Math.max(0, overlap));
96
101
  }, /* @ts-ignore */
97
102
  ...(ngDevMode ? [{ debugName: "overlap" }] : /* istanbul ignore next */ []));
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-avoiding-view.js","sourceRoot":"","sources":["../src/keyboard-avoiding-view.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n effect,\n inject,\n input,\n signal,\n booleanAttribute,\n} from '@angular/core';\nimport { Keyboard, LayoutAnimation, type LayoutEasing } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport type { Rect } from './events.ts';\nimport { ContentContainer } from './content-container.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\nimport { numberOr } from './transforms.ts';\n\nexport type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';\n\n/**\n * The names `LayoutEasing` knows. `'keyboard'` is among them: iOS reports its keyboard's own\n * curve by that name, and RN's KeyboardAvoidingView animates on it.\n */\nconst LAYOUT_EASINGS: ReadonlySet<string> = new Set([\n 'linear',\n 'easeInEaseOut',\n 'easeIn',\n 'easeOut',\n 'spring',\n 'keyboard',\n]);\n\n/** A keyboard event's `easing`, narrowed to what `LayoutAnimation` accepts, or left to its default. */\nfunction layoutEasingOf(easing: string | undefined): LayoutEasing | undefined {\n return easing !== undefined && LAYOUT_EASINGS.has(easing) ? (easing as LayoutEasing) : undefined;\n}\n\n/**\n * Moves its content clear of the keyboard.\n *\n * RN's KeyboardAvoidingView is a JS composite: there is no native component, only a view whose\n * padding, height or position tracks the keyboard. Off a device the `Keyboard` service reports\n * nothing, so this renders as a plain view and does nothing, which is also the right behaviour on\n * a platform with no soft keyboard.\n *\n * The overlap is the view's own frame against the keyboard's top edge, so a view that does not\n * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is\n * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in\n * the parent's coordinates, and on a screen under a navigation bar those start a bar's height\n * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by\n * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the\n * view's own height either: padding a view by more than it has pushes its content out of it.\n * When the keyboard source reports no `screenY`, the keyboard's height stands in.\n *\n * The adjustment itself animates with the keyboard's own motion, the way RN's own\n * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next\n * layout animation before the style change that moves this view, so the two run together rather\n * than the padding, height or position jumping to its resting place in one step. Android animates\n * no layout unless an app turns that on, and reports no duration, so there the view jumps; with\n * `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.\n */\n@Component({\n selector: 'keyboard-avoiding-view',\n imports: [ContentContainer, View],\n template: `\n @if (behavior() === 'position') {\n <view\n [contentContainerOf]=\"node\"\n [contentContainerClass]=\"contentContainerClass()\"\n [style]=\"positioned()\"\n ><ng-content\n /></view>\n } @else {\n <ng-content />\n }\n `,\n host: {\n '[styleOverride]': 'avoidance()',\n '(layout)': 'onLayout($event)',\n },\n})\nexport class KeyboardAvoidingView extends ViewBase {\n /**\n * How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this\n * unset and does nothing; `padding` is the default here because that is what a form wants.\n */\n readonly behavior = input<KeyboardAvoidingBehavior>('padding');\n /** Styles for the inner view that `position` moves. */\n readonly contentContainerStyle = input<Record<string, unknown>>();\n /**\n * Classes for the inner view that `position` moves, matched as if it were written in the\n * template this view is. See `ContentContainer`.\n */\n readonly contentContainerClass = input<string>();\n /** Set to false to stop avoiding without removing the view. */\n readonly enabled = input(true, { transform: booleanAttribute });\n /**\n * Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a\n * header's height; here the view is measured in the window, so a navigation bar needs none.\n */\n readonly keyboardVerticalOffset = input(0, { transform: numberOr(0) });\n\n private readonly keyboardService = inject(Keyboard);\n /** Where the keyboard is, at every frame of its motion where the platform says. */\n private readonly keyboard = this.keyboardService.position;\n private readonly layoutAnimation = inject(LayoutAnimation);\n private readonly frame = signal<Rect | null>(null);\n /** The height before any adjustment, which `height` mode shrinks from. */\n private initialHeight: number | null = null;\n\n /** How far the keyboard intrudes into this view, in points. */\n protected readonly overlap = computed(() => {\n if (!this.enabled()) return 0;\n const { height, screenY } = this.keyboard();\n if (height === 0) return 0;\n const frame = this.frame();\n const offset = this.keyboardVerticalOffset();\n if (!frame) return Math.max(0, height - offset);\n // In `height` mode the frame is the one this view already shrank, so measure the height it\n // had before, as RN adds its current adjustment back; otherwise the room it made reads as no\n // overlap and the height springs back under the keyboard.\n const extent =\n this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;\n const overlap = screenY === undefined ? height - offset : frame.y + extent - (screenY - offset);\n return Math.min(extent, Math.max(0, overlap));\n });\n\n /**\n * The adjustment, or nothing to adjust. Bound as the engine's style override rather than as\n * host style, because Angular ranks a host style binding under the template's: a caller's\n * `[style]=\"{flex: 1}\"` would otherwise keep its flex in height mode and the view would not\n * shrink at all. RN composes the adjustment after the caller's style, so it wins there too.\n */\n protected readonly avoidance = computed(() => {\n const overlap = this.overlap();\n const behavior = this.behavior();\n if (overlap === 0 || behavior === 'position') return undefined;\n if (behavior === 'height') {\n const base = this.initialHeight ?? this.frame()?.height;\n return base === undefined ? undefined : { height: base - overlap, flex: 0 };\n }\n return { paddingBottom: overlap };\n });\n\n protected readonly positioned = computed(() => ({\n ...this.contentContainerStyle(),\n bottom: this.overlap(),\n }));\n\n constructor() {\n super();\n // A duration of zero is Android's own keyboard event, which says nothing of the motion; there\n // is nothing to configure alongside it. Nor where the view follows the motion frame by frame.\n // `appear`/`leave` are turned off because this is an update to a view already on screen, not\n // one appearing or leaving - RN's own bare `LayoutAnimation.configureNext({duration, update})`\n // animates nothing else either.\n effect(() => {\n const { duration, easing } = this.keyboardService.metrics();\n if (!this.enabled() || !duration || this.keyboardService.followsMotion) return;\n void this.layoutAnimation.animate(() => {}, {\n duration,\n easing: layoutEasingOf(easing),\n appear: 'none',\n leave: 'none',\n });\n });\n }\n\n protected onLayout(event: NativeSyntheticEvent<{ layout?: Rect }>): void {\n const layout = event.nativeEvent?.layout;\n if (!layout) return;\n this.frame.set({ ...layout, y: this.windowTop(layout.y) });\n // Only a layout that the keyboard did not cause counts as the natural height.\n if (this.overlap() === 0) this.initialHeight = layout.height;\n }\n\n /**\n * Where this view's top edge is in the window, or `fallback` where the platform cannot say.\n * Synchronous on the new architecture, so it answers for the layout being reported.\n */\n private windowTop(fallback: number): number {\n let top = fallback;\n this.engine.measure(this.node, (frame) => (top = frame.y));\n return top;\n }\n}\n"]}
1
+ {"version":3,"file":"keyboard-avoiding-view.js","sourceRoot":"","sources":["../src/keyboard-avoiding-view.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n effect,\n inject,\n input,\n signal,\n booleanAttribute,\n} from '@angular/core';\nimport { Keyboard, LayoutAnimation, type LayoutEasing } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport type { Rect } from './events.ts';\nimport { ContentContainer } from './content-container.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\nimport { numberOr } from './transforms.ts';\n\nexport type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';\n\n/**\n * The names `LayoutEasing` knows. `'keyboard'` is among them: iOS reports its keyboard's own\n * curve by that name, and RN's KeyboardAvoidingView animates on it.\n */\nconst LAYOUT_EASINGS: ReadonlySet<string> = new Set([\n 'linear',\n 'easeInEaseOut',\n 'easeIn',\n 'easeOut',\n 'spring',\n 'keyboard',\n]);\n\n/** A keyboard event's `easing`, narrowed to what `LayoutAnimation` accepts, or left to its default. */\nfunction layoutEasingOf(easing: string | undefined): LayoutEasing | undefined {\n return easing !== undefined && LAYOUT_EASINGS.has(easing) ? (easing as LayoutEasing) : undefined;\n}\n\n/**\n * Moves its content clear of the keyboard.\n *\n * RN's KeyboardAvoidingView is a JS composite: there is no native component, only a view whose\n * padding, height or position tracks the keyboard. Off a device the `Keyboard` service reports\n * nothing, so this renders as a plain view and does nothing, which is also the right behaviour on\n * a platform with no soft keyboard.\n *\n * The overlap is the view's own frame against the keyboard's top edge, so a view that does not\n * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is\n * measured in the window, and the keyboard's `screenY` brought into it: `(layout)` reports the top\n * in the parent's coordinates, and on a screen under a navigation bar those start a bar's height\n * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by\n * exactly that much, its bottom edge still behind the keyboard. On Android, when React Native's\n * edge-to-edge flag is off (Expo Go leaves it off), the window starts below the status bar while\n * `screenY` counts from the top of the screen, so the keyboard's top is taken `Keyboard.windowTop`\n * higher in the window, or the view comes up short by the status bar; when the flag is on,\n * `Keyboard.windowTop` is zero. The overlap never exceeds the\n * view's own height either: padding a view by more than it has pushes its content out of it.\n * When the keyboard source reports no `screenY`, the keyboard's height stands in.\n *\n * The adjustment itself animates with the keyboard's own motion, the way RN's own\n * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next\n * layout animation before the style change that moves this view, so the two run together rather\n * than the padding, height or position jumping to its resting place in one step. Android animates\n * no layout unless an app turns that on, and reports no duration, so there the view jumps; with\n * `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.\n */\n@Component({\n selector: 'keyboard-avoiding-view',\n imports: [ContentContainer, View],\n template: `\n @if (behavior() === 'position') {\n <view\n [contentContainerOf]=\"node\"\n [contentContainerClass]=\"contentContainerClass()\"\n [style]=\"positioned()\"\n ><ng-content\n /></view>\n } @else {\n <ng-content />\n }\n `,\n host: {\n '[styleOverride]': 'avoidance()',\n '(layout)': 'onLayout($event)',\n },\n})\nexport class KeyboardAvoidingView extends ViewBase {\n /**\n * How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this\n * unset and does nothing; `padding` is the default here because that is what a form wants.\n */\n readonly behavior = input<KeyboardAvoidingBehavior>('padding');\n /** Styles for the inner view that `position` moves. */\n readonly contentContainerStyle = input<Record<string, unknown>>();\n /**\n * Classes for the inner view that `position` moves, matched as if it were written in the\n * template this view is. See `ContentContainer`.\n */\n readonly contentContainerClass = input<string>();\n /** Set to false to stop avoiding without removing the view. */\n readonly enabled = input(true, { transform: booleanAttribute });\n /**\n * Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a\n * header's height; here the view is measured in the window, so a navigation bar needs none.\n */\n readonly keyboardVerticalOffset = input(0, { transform: numberOr(0) });\n\n private readonly keyboardService = inject(Keyboard);\n /** Where the keyboard is, at every frame of its motion where the platform says. */\n private readonly keyboard = this.keyboardService.position;\n private readonly layoutAnimation = inject(LayoutAnimation);\n private readonly frame = signal<Rect | null>(null);\n /** The height before any adjustment, which `height` mode shrinks from. */\n private initialHeight: number | null = null;\n\n /** How far the keyboard intrudes into this view, in points. */\n protected readonly overlap = computed(() => {\n if (!this.enabled()) return 0;\n const { height, screenY } = this.keyboard();\n if (height === 0) return 0;\n const frame = this.frame();\n const offset = this.keyboardVerticalOffset();\n if (!frame) return Math.max(0, height - offset);\n // In `height` mode the frame is the one this view already shrank, so measure the height it\n // had before, as RN adds its current adjustment back; otherwise the room it made reads as no\n // overlap and the height springs back under the keyboard.\n const extent =\n this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;\n const top = screenY === undefined ? undefined : screenY - this.keyboardService.windowTop;\n const overlap = top === undefined ? height - offset : frame.y + extent - (top - offset);\n return Math.min(extent, Math.max(0, overlap));\n });\n\n /**\n * The adjustment, or nothing to adjust. Bound as the engine's style override rather than as\n * host style, because Angular ranks a host style binding under the template's: a caller's\n * `[style]=\"{flex: 1}\"` would otherwise keep its flex in height mode and the view would not\n * shrink at all. RN composes the adjustment after the caller's style, so it wins there too.\n */\n protected readonly avoidance = computed(() => {\n const overlap = this.overlap();\n const behavior = this.behavior();\n if (overlap === 0 || behavior === 'position') return undefined;\n if (behavior === 'height') {\n const base = this.initialHeight ?? this.frame()?.height;\n return base === undefined ? undefined : { height: base - overlap, flex: 0 };\n }\n return { paddingBottom: overlap };\n });\n\n protected readonly positioned = computed(() => ({\n ...this.contentContainerStyle(),\n bottom: this.overlap(),\n }));\n\n constructor() {\n super();\n // A duration of zero is Android's own keyboard event, which says nothing of the motion; there\n // is nothing to configure alongside it. Nor where the view follows the motion frame by frame.\n // `appear`/`leave` are turned off because this is an update to a view already on screen, not\n // one appearing or leaving - RN's own bare `LayoutAnimation.configureNext({duration, update})`\n // animates nothing else either.\n effect(() => {\n const { duration, easing } = this.keyboardService.metrics();\n if (!this.enabled() || !duration || this.keyboardService.followsMotion) return;\n void this.layoutAnimation.animate(() => {}, {\n duration,\n easing: layoutEasingOf(easing),\n appear: 'none',\n leave: 'none',\n });\n });\n }\n\n protected onLayout(event: NativeSyntheticEvent<{ layout?: Rect }>): void {\n const layout = event.nativeEvent?.layout;\n if (!layout) return;\n this.frame.set({ ...layout, y: this.windowTop(layout.y) });\n // Only a layout that the keyboard did not cause counts as the natural height.\n if (this.overlap() === 0) this.initialHeight = layout.height;\n }\n\n /**\n * Where this view's top edge is in the window, or `fallback` where the platform cannot say.\n * Synchronous on the new architecture, so it answers for the layout being reported.\n */\n private windowTop(fallback: number): number {\n let top = fallback;\n this.engine.measure(this.node, (frame) => (top = frame.y));\n return top;\n }\n}\n"]}
@@ -161,7 +161,8 @@ export class KeyboardDock extends ViewBase {
161
161
  const edge = this.bottomEdge();
162
162
  if (edge === null || screenY === undefined)
163
163
  return height;
164
- return Math.max(0, edge - screenY);
164
+ // The edge is in the window, and on Android that can start below the status bar.
165
+ return Math.max(0, edge - (screenY - this.keyboard.windowTop));
165
166
  }, /* @ts-ignore */
166
167
  ...(ngDevMode ? [{ debugName: "padding" }] : /* istanbul ignore next */ []));
167
168
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,UAAU,EACV,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,MAAM,EACN,MAAM,EACN,KAAK,EACL,MAAM,EACN,SAAS,GAEV,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACzF,OAAO,EACL,SAAS,EACT,cAAc,EACd,gBAAgB,GAIjB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;;AAE1C;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,cAAc,CAAU,qBAAqB,EAAE;IACpF,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK;CACrB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC1D,CAAC;AAED,+FAA+F;AAC/F,MAAM,aAAa,GAAG;IACpB,MAAM,EAAE,CAAC,gBAAgB,EAAE,2BAA2B,EAAE,mBAAmB,CAAC;IAC5E,IAAI,EAAE,CAAC,QAAQ,CAAC;CACjB,CAAC;AAEF,+FAA+F;AAC/F,MAAM,GAAG,GAAG,MAAM,CAAC;AAEnB,+EAA+E;AAC/E,MAAM,WAAW,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AAElE,qFAAqF;AACrF,MAAM,SAAS,GAAG,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAIjF,6FAA6F;AAK7F,MAAM,OAAO,sBAAsB;IACjC;QACE,gBAAgB,CAAC,0BAA0B,EAAE,wBAAwB,CAAC,CAAC;QACvE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC,CAAC;IAC1D,CAAC;uGAJU,sBAAsB;2FAAtB,sBAAsB;;2FAAtB,sBAAsB;kBAJlC,SAAS;mBAAC;oBACT,QAAQ,EAAE,0BAA0B;oBACpC,IAAI,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE;iBAC9B;;AAQD;;;GAGG;AAEH,MAAM,OAAO,kBAAkB;IAC7B;QACE,MAAM,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;QACvC,IAAI,MAAM;YAAE,gBAAgB,CAAC,sBAAsB,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC,CAAC;IAC1D,CAAC;uGALU,kBAAkB;2FAAlB,kBAAkB;;2FAAlB,kBAAkB;kBAD9B,SAAS;mBAAC,EAAE,QAAQ,EAAE,sBAAsB,EAAE;;AAS/C;;;GAGG;AAKH,MAAM,OAAO,mBAAmB;IACrB,MAAM,GAAG,KAAK;0FAAU,CAAC;IACzB,iBAAiB,GAAG,KAAK;qGAAU,CAAC;IAE7C;QACE,gBAAgB,CAAC,uBAAuB,EAAE,qBAAqB,CAAC,CAAC;QACjE,SAAS,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC,CAAC;IAC1D,CAAC;uGAPU,mBAAmB;2FAAnB,mBAAmB;;2FAAnB,mBAAmB;kBAJ/B,SAAS;mBAAC;oBACT,QAAQ,EAAE,uBAAuB;oBACjC,IAAI,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,qBAAqB,EAAE,qBAAqB,EAAE;iBAC/E;;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AA4CH,MAAM,OAAO,YAAa,SAAQ,QAAQ;IACxC,qFAAqF;IAC5E,eAAe,GAAG,KAAK;mGAAU,CAAC;IAE1B,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5B,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1B,IAAI,GAAG,EAAE,CAAC;IACV,SAAS,GAAG,WAAW,CAAC;IACxB,QAAQ,GAAG,SAAS,CAAC;IAExC,wFAAwF;IACrE,MAAM,GAAG,cAAc,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE5F;;;OAGG;IACgB,KAAK,GACtB,IAAI,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,EAAE,KAAK,KAAK,IAAI,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAErE,KAAK,GAAG,YAAY,CAAC,SAAS,6EAAI,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,CAAC;IACzE,UAAU,GAAG,YAAY,CAAC,SAAS,kFAAI,WAAW,EAAE,IAAI,GAAG,CAAC;IAE7E,wFAAwF;IACvE,UAAU,GAAG,MAAM,CAAgB,IAAI;mFAAC,CAAC;IAC1D,4FAA4F;IACzE,SAAS,GAAG,MAAM,CAAC,CAAC;kFAAC,CAAC;IACzC;;;OAGG;IACc,UAAU,GAAG,MAAM,CAAC,CAAC;mFAAC,CAAC;IAExC,gGAAgG;IAC/E,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM;8EAAC,CAAC;IAE9D,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE,CAC/B,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;gFAC/D,CAAC;IAEiB,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE;QACzC,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACpD,IAAI,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAC/B,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC;IACrC,CAAC;gFAAC,CAAC;IAEH;;;OAGG;IACgB,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,EAAE,aAAqC,CAAC;QACjE,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;QAC3C,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;QACpB,MAAM,EAAE,GAAG,iBAAiB,EAAE,KAAK,EAAE,CAAC;QACtC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3D,OAAO,EAAE,CAAC;IACZ,CAAC;gFAAC,CAAC;IAEH,iGAAiG;IACxF,SAAS,GAAG,QAAQ,CAAc,GAAG,EAAE;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QAC3B,OAAO,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,GAAG,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5D,CAAC;kFAAC,CAAC;IAEK,MAAM,GAAoB,IAAI,CAAC;IAEtB,OAAO,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;IAEnD;QACE,KAAK,EAAE,CAAC;QACR,IAAI,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,4FAA4F;QAC5F,kCAAkC;QAClC,MAAM,KAAK,GAAG,MAAM,CAClB,GAAG,EAAE;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,EAAE;gBACb,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;oBAAE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtE,CAAC,CAAC,CAAC;QACL,CAAC,6EACC,QAAQ,EAAE,MAAM,CAAC,mBAAmB,CAAC,EAAE,aAAa,EAAE,IAAI,GAC7D,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,IAAc,EAAE,QAAuB,IAAI;QAC9C,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC;IAC/F,CAAC;IAEO,OAAO;QACb,IAAI,KAAK,GAAuB,IAAI,CAAC;QACrC,iBAAiB,CAAC,GAAG,EAAE;YACrB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,EAAE;gBACb,IAAI,KAAK;oBAAE,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;gBAC3B,IAAI,GAAG;oBAAE,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,oFAAoF;IAC5E,cAAc;QACpB,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC;QAC/F,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,gEAAgE;IACxD,OAAO;QACb,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,CAAC;IAES,cAAc,CAAC,KAAc;QACrC,MAAM,MAAM,GAAI,KAA2B,CAAC,WAAW,EAAE,MAAM,CAAC;QAChE,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACxD,CAAC;IAES,WAAW,CAAC,KAAyD;QAC7E,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC;QACjD,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACvD,CAAC;IAES,QAAQ;QAChB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IACzF,CAAC;uGA3IU,YAAY;2FAAZ,YAAY,iaAoBe,SAAS,2BAA6B,UAAU,0EAC3C,SAAS,mHA1D1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BT,4DAtGU,sBAAsB,qEA4BtB,mBAAmB,2GAhBnB,kBAAkB,iEAyD8C,YAAY,4GAAE,IAAI;;2FAwClF,YAAY;kBA3CxB,SAAS;mBAAC;oBACT,QAAQ,EAAE,eAAe;oBACzB,QAAQ,EAAE,cAAc;oBACxB,OAAO,EAAE,CAAC,sBAAsB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,YAAY,EAAE,IAAI,CAAC;oBAC9F,gGAAgG;oBAChG,kEAAkE;oBAClE,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BT;oBACD,IAAI,EAAE;wBACJ,uBAAuB,EAAE,WAAW;wBACpC,yBAAyB,EAAE,mBAAmB;wBAC9C,UAAU,EAAE,YAAY;qBACzB;iBACF;kOAqBuC,SAAS,QAAE,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,yFAC7C,SAAS,QAAE,EAAE,WAAW,EAAE,IAAI,EAAE;AAyH7E,IAAI,KAAK,GAAG,CAAC,CAAC;AAEd;;;;;;;;;;GAUG;AAKH,MAAM,OAAO,YAAY;IACd,IAAI,GAAG,KAAK,CAAC,QAAQ,2EAAiB,KAAK,EAAE,cAAc,GAAG,CAAC;IAErD,OAAO,GAAG,QAAQ,CAAC,GAAG,EAAE;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;QACtC,OAAO,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3C,CAAC;gFAAC,CAAC;IAEH;QACE,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;QAC1D,IAAI,KAAK,GAAuB,IAAI,CAAC;QACrC,iBAAiB,CAAC,GAAG,EAAE;YACrB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YACzB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YAC/B,SAAS,CAAC,GAAG,EAAE;gBACb,IAAI,CAAC,KAAK;oBAAE,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;gBAC7C,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;gBACrB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;uGAtBU,YAAY;2FAAZ,YAAY;;2FAAZ,YAAY;kBAJxB,SAAS;mBAAC;oBACT,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE,EAAE,uBAAuB,EAAE,WAAW,EAAE;iBAC/C","sourcesContent":["import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n EnvironmentInjector,\n InjectionToken,\n afterRenderEffect,\n computed,\n contentChild,\n effect,\n inject,\n input,\n signal,\n untracked,\n type Provider,\n} from '@angular/core';\nimport { KEYBOARD_MOTION, Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';\nimport {\n claimHost,\n nativePlatform,\n registerViewName,\n type HostNode,\n type ScrollDrive,\n type ScrollRange,\n} from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { TextInput } from './text-input.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`\n * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.\n */\nexport const KEYBOARD_CONTROLLER = new InjectionToken<boolean>('KEYBOARD_CONTROLLER', {\n providedIn: 'root',\n factory: () => false,\n});\n\n/**\n * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.\n * Nothing is imported from it: the dock drives its native views directly.\n */\nexport function provideKeyboardController(): Provider {\n return { provide: KEYBOARD_CONTROLLER, useValue: true };\n}\n\n/** What carries the keyboard's height, frame by frame, in react-native-keyboard-controller. */\nconst KEYBOARD_FEED = {\n events: ['onKeyboardMove', 'onKeyboardMoveInteractive', 'onKeyboardMoveEnd'],\n path: ['height'],\n};\n\n/** Far enough that no keyboard is taller; native interpolation takes numbers, not infinity. */\nconst FAR = 10_000;\n\n/** A native view the dock renders for itself, out of the way of the layout. */\nconst OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };\n\n/** Over the dock, out of the way of the layout, so its bottom edge is the dock's. */\nconst OVER_DOCK = { position: 'absolute', top: 0, bottom: 0, left: 0, width: 0 };\n\ntype KeyboardMoveEvent = { nativeEvent?: { height?: number } };\n\n/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */\n@Directive({\n selector: 'keyboard-controller-view',\n host: { '[enabled]': 'true' },\n})\nexport class KeyboardControllerView {\n constructor() {\n registerViewName('keyboard-controller-view', 'KeyboardControllerView');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at\n * every frame of its motion. See `KeyboardDock`.\n */\n@Directive({ selector: 'keyboard-motion-view' })\nexport class KeyboardMotionView {\n constructor() {\n const motion = inject(KEYBOARD_MOTION);\n if (motion) registerViewName('keyboard-motion-view', motion.viewName);\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with\n * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.\n */\n@Directive({\n selector: 'keyboard-gesture-area',\n host: { '[offset]': 'offset()', '[textInputNativeID]': 'textInputNativeID()' },\n})\nexport class KeyboardGestureArea {\n readonly offset = input<number>();\n readonly textInputNativeID = input<string>();\n\n constructor() {\n registerViewName('keyboard-gesture-area', 'KeyboardGestureArea');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.\n * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard\n * is down; on top of it while it is up.\n *\n * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved\n * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input\n * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll\n * view with `keyboardDismissMode=\"interactive\"`, where iOS reports no keyboard frames to\n * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of\n * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves\n * content above with it.\n *\n * On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by\n * a view of that package's that reports how far the keyboard covers the dock at every frame. It is\n * measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet\n * react-native-screens raises with the keyboard, rises only as far as it still must. Android does\n * not use react-native-keyboard-controller: its view adds the navigation bar as margins around the\n * whole app's content, which an app drawn edge to edge does not want.\n *\n * Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the\n * keyboard starts to move; on Android it jumps into place then, rather than moving with the\n * keyboard. A window the system resized for the keyboard already puts it clear, and then it is not\n * padded at all.\n *\n * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own\n * place, for content above that keeps clear of it with padding or an inset rather than moving.\n */\n@Component({\n selector: 'keyboard-dock',\n exportAs: 'keyboardDock',\n imports: [KeyboardControllerView, KeyboardGestureArea, KeyboardMotionView, TemplateSlot, View],\n // One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would\n // be given the content at compile time, whichever branch renders.\n template: `\n <ng-template #content><ng-content /></ng-template>\n @if (lifts) {\n @if (motion) {\n <keyboard-motion-view\n [style]=\"overDock\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n } @else {\n <keyboard-controller-view\n [style]=\"outOfFlow\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n <keyboard-gesture-area\n [style]=\"outOfFlow\"\n [offset]=\"barHeight()\"\n [textInputNativeID]=\"inputID()\"\n />\n }\n <view\n collapsable=\"false\"\n [style.backgroundColor]=\"backgroundColor()\"\n (layout)=\"onBarLayout($event)\"\n ><ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\"\n /></view>\n } @else {\n <ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\" />\n }\n `,\n host: {\n '[style.paddingBottom]': 'padding()',\n '[style.backgroundColor]': 'backgroundColor()',\n '(layout)': 'onLayout()',\n },\n})\nexport class KeyboardDock extends ViewBase {\n /** The bar's background, under the inset it sits above as well as the bar itself. */\n readonly backgroundColor = input<string>();\n\n private readonly keyboard = inject(Keyboard);\n private readonly safeArea = inject(SafeArea);\n protected readonly none = {};\n protected readonly outOfFlow = OUT_OF_FLOW;\n protected readonly overDock = OVER_DOCK;\n\n /** On Android, the view that reports the keyboard's motion, where the build has one. */\n protected readonly motion = nativePlatform() === 'android' ? inject(KEYBOARD_MOTION) : null;\n\n /**\n * Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:\n * neither changes under an app.\n */\n protected readonly lifts =\n this.motion !== null || (nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER));\n\n private readonly field = contentChild(TextInput, { descendants: true, read: ElementRef });\n private readonly fieldInput = contentChild(TextInput, { descendants: true });\n\n /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */\n private readonly bottomEdge = signal<number | null>(null);\n /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */\n protected readonly barHeight = signal(0);\n /**\n * The keyboard's height over the dock as native last reported it: the controller's keyboard\n * height, excluding the drag area, or the motion view's cover of the dock.\n */\n private readonly controlled = signal(0);\n\n /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */\n private readonly inset = computed(() => this.safeArea.insets().bottom);\n\n readonly covered = computed(() =>\n this.lifts ? Math.max(0, this.controlled() - this.inset()) : 0,\n );\n\n protected readonly padding = computed(() => {\n if (this.lifts) return this.inset();\n const { height, screenY } = this.keyboard.metrics();\n if (height === 0) return this.inset();\n const edge = this.bottomEdge();\n if (edge === null || screenY === undefined) return height;\n return Math.max(0, edge - screenY);\n });\n\n /**\n * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field\n * without one is given one.\n */\n protected readonly inputID = computed(() => {\n const input = this.fieldInput();\n const node = this.field()?.nativeElement as HostNode | undefined;\n if (!input || !node) return undefined;\n const own = input.nativeID() ?? input.id();\n if (own) return own;\n const id = `keyboard-dock-${++docks}`;\n untracked(() => this.engine.setProp(node, 'nativeID', id));\n return id;\n });\n\n /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */\n readonly liftRange = computed<ScrollRange>(() => {\n const inset = this.inset();\n return { input: [inset, inset + FAR], output: [0, -FAR] };\n });\n\n private source: HostNode | null = null;\n\n private readonly inFront = inject(SCREEN_IN_FRONT);\n\n constructor() {\n super();\n if (this.lifts) this.liftBar();\n // A root effect, because a covered screen's views are detached from change detection, and a\n // view's own effects wait for it.\n const front = effect(\n () => {\n const inFront = this.inFront();\n untracked(() => {\n if (!inFront && this.keyboard.height() > 0) this.keyboard.dismiss();\n });\n },\n { injector: inject(EnvironmentInjector), manualCleanup: true },\n );\n inject(DestroyRef).onDestroy(() => front.destroy());\n }\n\n /**\n * Move a view with the keyboard on the native side, as the bar is moved: content above the bar\n * that should rise with it. Null when native does not move the bar. See `keyboardLift`.\n */\n lift(view: HostNode, shift: number | null = null): ScrollDrive | null {\n if (!this.lifts) return null;\n const source = this.controllerView();\n const range = this.liftRange();\n return this.engine.driveByEvent(view, source, KEYBOARD_FEED, 'translateY', range, [], shift);\n }\n\n private liftBar(): void {\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const range = this.liftRange();\n untracked(() => {\n if (drive) return drive.update(range);\n const bar = this.barView();\n if (bar) drive = this.lift(bar);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n\n /** The controller's or the motion view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.childList.find((child) => child.nodeKind === 'element') ?? this.node;\n return this.source;\n }\n\n /** The view holding the content, the last of the dock's own. */\n private barView(): HostNode | undefined {\n return this.node.childList.filter((child) => child.nodeKind === 'element').at(-1);\n }\n\n protected onKeyboardMove(event: unknown): void {\n const height = (event as KeyboardMoveEvent).nativeEvent?.height;\n if (height !== undefined) this.controlled.set(height);\n }\n\n protected onBarLayout(event: { nativeEvent?: { layout?: { height?: number } } }): void {\n const height = event.nativeEvent?.layout?.height;\n if (height !== undefined) this.barHeight.set(height);\n }\n\n protected onLayout(): void {\n if (this.lifts) return;\n this.engine.measure(this.node, (frame) => this.bottomEdge.set(frame.y + frame.height));\n }\n}\n\nlet docks = 0;\n\n/**\n * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]=\"dock\">` around a chat's\n * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is\n * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than\n * drawn over what is above. Does nothing where native does not move the bar.\n *\n * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved\n * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view\n * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,\n * and a transform does not change what it thinks.\n */\n@Directive({\n selector: '[keyboardLift]',\n host: { '[style.paddingBottom]': 'padding()' },\n})\nexport class KeyboardLift {\n readonly dock = input.required<KeyboardDock>({ alias: 'keyboardLift' });\n\n protected readonly padding = computed(() => {\n const covered = this.dock().covered();\n return covered > 0 ? covered : undefined;\n });\n\n constructor() {\n const view = inject(ElementRef).nativeElement as HostNode;\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const dock = this.dock();\n const range = dock.liftRange();\n const covered = dock.covered();\n untracked(() => {\n if (!drive) drive = dock.lift(view, covered);\n drive?.update(range);\n drive?.shift(covered);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n}\n"]}
1
+ {"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n EnvironmentInjector,\n InjectionToken,\n afterRenderEffect,\n computed,\n contentChild,\n effect,\n inject,\n input,\n signal,\n untracked,\n type Provider,\n} from '@angular/core';\nimport { KEYBOARD_MOTION, Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';\nimport {\n claimHost,\n nativePlatform,\n registerViewName,\n type HostNode,\n type ScrollDrive,\n type ScrollRange,\n} from '@ng-native/fabric';\nimport { TemplateSlot } from './template-slot.ts';\nimport { TextInput } from './text-input.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/**\n * Whether react-native-keyboard-controller is installed, which is what lets a `<keyboard-dock>`\n * on iOS follow the keyboard frame by frame. See `provideKeyboardController`.\n */\nexport const KEYBOARD_CONTROLLER = new InjectionToken<boolean>('KEYBOARD_CONTROLLER', {\n providedIn: 'root',\n factory: () => false,\n});\n\n/**\n * Say that react-native-keyboard-controller is installed, for `<keyboard-dock>` to use on iOS.\n * Nothing is imported from it: the dock drives its native views directly.\n */\nexport function provideKeyboardController(): Provider {\n return { provide: KEYBOARD_CONTROLLER, useValue: true };\n}\n\n/** What carries the keyboard's height, frame by frame, in react-native-keyboard-controller. */\nconst KEYBOARD_FEED = {\n events: ['onKeyboardMove', 'onKeyboardMoveInteractive', 'onKeyboardMoveEnd'],\n path: ['height'],\n};\n\n/** Far enough that no keyboard is taller; native interpolation takes numbers, not infinity. */\nconst FAR = 10_000;\n\n/** A native view the dock renders for itself, out of the way of the layout. */\nconst OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };\n\n/** Over the dock, out of the way of the layout, so its bottom edge is the dock's. */\nconst OVER_DOCK = { position: 'absolute', top: 0, bottom: 0, left: 0, width: 0 };\n\ntype KeyboardMoveEvent = { nativeEvent?: { height?: number } };\n\n/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */\n@Directive({\n selector: 'keyboard-controller-view',\n host: { '[enabled]': 'true' },\n})\nexport class KeyboardControllerView {\n constructor() {\n registerViewName('keyboard-controller-view', 'KeyboardControllerView');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at\n * every frame of its motion. See `KeyboardDock`.\n */\n@Directive({ selector: 'keyboard-motion-view' })\nexport class KeyboardMotionView {\n constructor() {\n const motion = inject(KEYBOARD_MOTION);\n if (motion) registerViewName('keyboard-motion-view', motion.viewName);\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with\n * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.\n */\n@Directive({\n selector: 'keyboard-gesture-area',\n host: { '[offset]': 'offset()', '[textInputNativeID]': 'textInputNativeID()' },\n})\nexport class KeyboardGestureArea {\n readonly offset = input<number>();\n readonly textInputNativeID = input<string>();\n\n constructor() {\n registerViewName('keyboard-gesture-area', 'KeyboardGestureArea');\n claimHost(inject(ElementRef).nativeElement as HostNode);\n }\n}\n\n/**\n * A bar that sits on the keyboard: a chat's composer, a comment box, a row of formatting keys.\n * At the bottom of its parent, clear of the home indicator or navigation bar, while the keyboard\n * is down; on top of it while it is up.\n *\n * On iOS with react-native-keyboard-controller (`provideKeyboardController()`), the bar is moved\n * by the keyboard's height on the native side, frame by frame, the way UIKit carries an input\n * accessory: as the keyboard rises and falls, and while a finger drags it down through a scroll\n * view with `keyboardDismissMode=\"interactive\"`, where iOS reports no keyboard frames to\n * JavaScript at all. The drag takes the keyboard from the top of the bar rather than the top of\n * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves\n * content above with it.\n *\n * On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by\n * a view of that package's that reports how far the keyboard covers the dock at every frame. It is\n * measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet\n * react-native-screens raises with the keyboard, rises only as far as it still must. Android does\n * not use react-native-keyboard-controller: its view adds the navigation bar as margins around the\n * whole app's content, which an app drawn edge to edge does not want.\n *\n * Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the\n * keyboard starts to move; on Android it jumps into place then, rather than moving with the\n * keyboard. A window the system resized for the keyboard already puts it clear, and then it is not\n * padded at all.\n *\n * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own\n * place, for content above that keeps clear of it with padding or an inset rather than moving.\n */\n@Component({\n selector: 'keyboard-dock',\n exportAs: 'keyboardDock',\n imports: [KeyboardControllerView, KeyboardGestureArea, KeyboardMotionView, TemplateSlot, View],\n // One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would\n // be given the content at compile time, whichever branch renders.\n template: `\n <ng-template #content><ng-content /></ng-template>\n @if (lifts) {\n @if (motion) {\n <keyboard-motion-view\n [style]=\"overDock\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n } @else {\n <keyboard-controller-view\n [style]=\"outOfFlow\"\n (keyboardMoveStart)=\"onKeyboardMove($event)\"\n (keyboardMoveEnd)=\"onKeyboardMove($event)\"\n />\n <keyboard-gesture-area\n [style]=\"outOfFlow\"\n [offset]=\"barHeight()\"\n [textInputNativeID]=\"inputID()\"\n />\n }\n <view\n collapsable=\"false\"\n [style.backgroundColor]=\"backgroundColor()\"\n (layout)=\"onBarLayout($event)\"\n ><ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\"\n /></view>\n } @else {\n <ng-container [templateSlot]=\"content\" [templateSlotContext]=\"none\" />\n }\n `,\n host: {\n '[style.paddingBottom]': 'padding()',\n '[style.backgroundColor]': 'backgroundColor()',\n '(layout)': 'onLayout()',\n },\n})\nexport class KeyboardDock extends ViewBase {\n /** The bar's background, under the inset it sits above as well as the bar itself. */\n readonly backgroundColor = input<string>();\n\n private readonly keyboard = inject(Keyboard);\n private readonly safeArea = inject(SafeArea);\n protected readonly none = {};\n protected readonly outOfFlow = OUT_OF_FLOW;\n protected readonly overDock = OVER_DOCK;\n\n /** On Android, the view that reports the keyboard's motion, where the build has one. */\n protected readonly motion = nativePlatform() === 'android' ? inject(KEYBOARD_MOTION) : null;\n\n /**\n * Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:\n * neither changes under an app.\n */\n protected readonly lifts =\n this.motion !== null || (nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER));\n\n private readonly field = contentChild(TextInput, { descendants: true, read: ElementRef });\n private readonly fieldInput = contentChild(TextInput, { descendants: true });\n\n /** Where the dock's bottom edge is in the window, which the keyboard is compared to. */\n private readonly bottomEdge = signal<number | null>(null);\n /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */\n protected readonly barHeight = signal(0);\n /**\n * The keyboard's height over the dock as native last reported it: the controller's keyboard\n * height, excluding the drag area, or the motion view's cover of the dock.\n */\n private readonly controlled = signal(0);\n\n /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */\n private readonly inset = computed(() => this.safeArea.insets().bottom);\n\n readonly covered = computed(() =>\n this.lifts ? Math.max(0, this.controlled() - this.inset()) : 0,\n );\n\n protected readonly padding = computed(() => {\n if (this.lifts) return this.inset();\n const { height, screenY } = this.keyboard.metrics();\n if (height === 0) return this.inset();\n const edge = this.bottomEdge();\n if (edge === null || screenY === undefined) return height;\n // The edge is in the window, and on Android that can start below the status bar.\n return Math.max(0, edge - (screenY - this.keyboard.windowTop));\n });\n\n /**\n * The field the drag area belongs to, by `nativeID`, which the controller finds it by. A field\n * without one is given one.\n */\n protected readonly inputID = computed(() => {\n const input = this.fieldInput();\n const node = this.field()?.nativeElement as HostNode | undefined;\n if (!input || !node) return undefined;\n const own = input.nativeID() ?? input.id();\n if (own) return own;\n const id = `keyboard-dock-${++docks}`;\n untracked(() => this.engine.setProp(node, 'nativeID', id));\n return id;\n });\n\n /** The bar, and anything `keyboardLift` moves, rises by the keyboard less the inset under it. */\n readonly liftRange = computed<ScrollRange>(() => {\n const inset = this.inset();\n return { input: [inset, inset + FAR], output: [0, -FAR] };\n });\n\n private source: HostNode | null = null;\n\n private readonly inFront = inject(SCREEN_IN_FRONT);\n\n constructor() {\n super();\n if (this.lifts) this.liftBar();\n // A root effect, because a covered screen's views are detached from change detection, and a\n // view's own effects wait for it.\n const front = effect(\n () => {\n const inFront = this.inFront();\n untracked(() => {\n if (!inFront && this.keyboard.height() > 0) this.keyboard.dismiss();\n });\n },\n { injector: inject(EnvironmentInjector), manualCleanup: true },\n );\n inject(DestroyRef).onDestroy(() => front.destroy());\n }\n\n /**\n * Move a view with the keyboard on the native side, as the bar is moved: content above the bar\n * that should rise with it. Null when native does not move the bar. See `keyboardLift`.\n */\n lift(view: HostNode, shift: number | null = null): ScrollDrive | null {\n if (!this.lifts) return null;\n const source = this.controllerView();\n const range = this.liftRange();\n return this.engine.driveByEvent(view, source, KEYBOARD_FEED, 'translateY', range, [], shift);\n }\n\n private liftBar(): void {\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const range = this.liftRange();\n untracked(() => {\n if (drive) return drive.update(range);\n const bar = this.barView();\n if (bar) drive = this.lift(bar);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n\n /** The controller's or the motion view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.childList.find((child) => child.nodeKind === 'element') ?? this.node;\n return this.source;\n }\n\n /** The view holding the content, the last of the dock's own. */\n private barView(): HostNode | undefined {\n return this.node.childList.filter((child) => child.nodeKind === 'element').at(-1);\n }\n\n protected onKeyboardMove(event: unknown): void {\n const height = (event as KeyboardMoveEvent).nativeEvent?.height;\n if (height !== undefined) this.controlled.set(height);\n }\n\n protected onBarLayout(event: { nativeEvent?: { layout?: { height?: number } } }): void {\n const height = event.nativeEvent?.layout?.height;\n if (height !== undefined) this.barHeight.set(height);\n }\n\n protected onLayout(): void {\n if (this.lifts) return;\n this.engine.measure(this.node, (frame) => this.bottomEdge.set(frame.y + frame.height));\n }\n}\n\nlet docks = 0;\n\n/**\n * Moves its view with a `<keyboard-dock>`'s bar: `<view [keyboardLift]=\"dock\">` around a chat's\n * transcript keeps the newest message on top of the bar as the keyboard rises, falls and is\n * dragged. Put it inside a view that clips, so what rises past the top is hidden rather than\n * drawn over what is above. Does nothing where native does not move the bar.\n *\n * Once the keyboard has moved, the view is padded by as much of it as covers the view, and moved\n * back down by the same amount, which leaves it where it was on screen: since iOS 26 a scroll view\n * the keyboard overlaps in layout draws a frosted edge effect over the part it thinks is covered,\n * and a transform does not change what it thinks.\n */\n@Directive({\n selector: '[keyboardLift]',\n host: { '[style.paddingBottom]': 'padding()' },\n})\nexport class KeyboardLift {\n readonly dock = input.required<KeyboardDock>({ alias: 'keyboardLift' });\n\n protected readonly padding = computed(() => {\n const covered = this.dock().covered();\n return covered > 0 ? covered : undefined;\n });\n\n constructor() {\n const view = inject(ElementRef).nativeElement as HostNode;\n let drive: ScrollDrive | null = null;\n afterRenderEffect(() => {\n const dock = this.dock();\n const range = dock.liftRange();\n const covered = dock.covered();\n untracked(() => {\n if (!drive) drive = dock.lift(view, covered);\n drive?.update(range);\n drive?.shift(covered);\n });\n });\n inject(DestroyRef).onDestroy(() => drive?.stop());\n }\n}\n"]}
@@ -1,11 +1,13 @@
1
- import type { HostEngine, HostNode } from '@ng-native/fabric';
1
+ import { type HostEngine, type HostNode } from '@ng-native/fabric';
2
2
  export type KeyboardShouldPersistTaps = 'always' | 'never' | 'handled';
3
3
  /**
4
4
  * What a tap in a scrolling container does while a text input has the keyboard up, which is
5
5
  * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):
6
- * a tap anywhere but on the input is the container's before a child sees it, and all it does is
6
+ * a tap anywhere but on a text input is the container's before a child sees it, and all it does is
7
7
  * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.
8
- * `always`: the keyboard stays.
8
+ * `always`: the keyboard stays. A tap on another text input never dismisses, so a form's next
9
+ * field takes the focus with the keyboard still up: React Native's `never` lets any text input's
10
+ * tap through, and with `handled` the text input takes the tap itself.
9
11
  *
10
12
  * Returns the responder's release, for the container's destroy.
11
13
  */
@@ -1,25 +1,19 @@
1
+ import { isTextInput, viewNameOf, } from '@ng-native/fabric';
1
2
  /**
2
3
  * What a tap in a scrolling container does while a text input has the keyboard up, which is
3
4
  * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):
4
- * a tap anywhere but on the input is the container's before a child sees it, and all it does is
5
+ * a tap anywhere but on a text input is the container's before a child sees it, and all it does is
5
6
  * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.
6
- * `always`: the keyboard stays.
7
+ * `always`: the keyboard stays. A tap on another text input never dismisses, so a form's next
8
+ * field takes the focus with the keyboard still up: React Native's `never` lets any text input's
9
+ * tap through, and with `handled` the text input takes the tap itself.
7
10
  *
8
11
  * Returns the responder's release, for the container's destroy.
9
12
  */
10
13
  export function dismissKeyboardOnTap(engine, node, policy) {
11
14
  let dismissing = false;
12
- /** A keyboard is up for an input, and the tap is not on that input. */
13
- const tapDismisses = (target) => {
14
- const focused = engine.focused;
15
- if (!focused)
16
- return false;
17
- for (let at = target; at; at = at.parent) {
18
- if (at === focused)
19
- return false;
20
- }
21
- return true;
22
- };
15
+ /** A keyboard is up for an input, and the tap is not on a text input or above the keyboard. */
16
+ const tapDismisses = (target) => engine.focusedField !== null && !isTextInput(target) && !inAccessoryBar(target);
23
17
  // RN's `_observedScrollSinceBecomingResponder`: a touch that scrolled was a drag, and a drag
24
18
  // through the content is reading it, so its release leaves the keyboard up.
25
19
  const stopScroll = engine.setEventListener(node, 'topScroll', () => {
@@ -35,9 +29,7 @@ export function dismissKeyboardOnTap(engine, node, policy) {
35
29
  if (!dismissing)
36
30
  return;
37
31
  dismissing = false;
38
- const focused = engine.focused;
39
- if (focused)
40
- engine.dispatchCommand(focused, 'blur');
32
+ engine.blurFocused();
41
33
  },
42
34
  onResponderTerminate: () => {
43
35
  dismissing = false;
@@ -48,4 +40,17 @@ export function dismissKeyboardOnTap(engine, node, policy) {
48
40
  stopResponder();
49
41
  };
50
42
  }
43
+ /**
44
+ * Whether the tap is on an `input-accessory-view` or inside one. On iOS the bar is drawn above the
45
+ * keyboard, outside the scroll view it is written in, so a tap on it is not a tap outside the field,
46
+ * as UIKit has it. React Native's `ScrollView.js` dismisses the keyboard for it all the same. On
47
+ * Android the element is a plain view, inside the scroll view on screen too.
48
+ */
49
+ function inAccessoryBar(target) {
50
+ for (let at = target; at; at = at.parent) {
51
+ if (viewNameOf(at) === 'InputAccessoryView')
52
+ return true;
53
+ }
54
+ return false;
55
+ }
51
56
  //# sourceMappingURL=keyboard-taps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-taps.js","sourceRoot":"","sources":["../src/keyboard-taps.ts"],"names":[],"mappings":"AAIA;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAkB,EAClB,IAAc,EACd,MAAuC;IAEvC,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,uEAAuE;IACvE,MAAM,YAAY,GAAG,CAAC,MAAkB,EAAW,EAAE;QACnD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,KAAK,IAAI,EAAE,GAAsB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;YAC5D,IAAI,EAAE,KAAK,OAAO;gBAAE,OAAO,KAAK,CAAC;QACnC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,6FAA6F;IAC7F,4EAA4E;IAC5E,MAAM,UAAU,GAAG,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;QACjE,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAC9C,gCAAgC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC;QAC7F,yBAAyB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,CAAC;QACxF,gBAAgB,EAAE,GAAG,EAAE;YACrB,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,kBAAkB,EAAE,GAAG,EAAE;YACvB,IAAI,CAAC,UAAU;gBAAE,OAAO;YACxB,UAAU,GAAG,KAAK,CAAC;YACnB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;YAC/B,IAAI,OAAO;gBAAE,MAAM,CAAC,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC;QACD,oBAAoB,EAAE,GAAG,EAAE;YACzB,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;KACF,CAAC,CAAC;IACH,OAAO,GAAG,EAAE;QACV,UAAU,EAAE,CAAC;QACb,aAAa,EAAE,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import type { EngineNode, HostEngine, HostNode } from '@ng-native/fabric';\n\nexport type KeyboardShouldPersistTaps = 'always' | 'never' | 'handled';\n\n/**\n * What a tap in a scrolling container does while a text input has the keyboard up, which is\n * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):\n * a tap anywhere but on the input is the container's before a child sees it, and all it does is\n * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.\n * `always`: the keyboard stays.\n *\n * Returns the responder's release, for the container's destroy.\n */\nexport function dismissKeyboardOnTap(\n engine: HostEngine,\n node: HostNode,\n policy: () => KeyboardShouldPersistTaps,\n): () => void {\n let dismissing = false;\n\n /** A keyboard is up for an input, and the tap is not on that input. */\n const tapDismisses = (target: EngineNode): boolean => {\n const focused = engine.focused;\n if (!focused) return false;\n for (let at: EngineNode | null = target; at; at = at.parent) {\n if (at === focused) return false;\n }\n return true;\n };\n\n // RN's `_observedScrollSinceBecomingResponder`: a touch that scrolled was a drag, and a drag\n // through the content is reading it, so its release leaves the keyboard up.\n const stopScroll = engine.setEventListener(node, 'topScroll', () => {\n dismissing = false;\n });\n const stopResponder = engine.setResponder(node, {\n onStartShouldSetResponderCapture: (_, target) => policy() === 'never' && tapDismisses(target),\n onStartShouldSetResponder: (_, target) => policy() === 'handled' && tapDismisses(target),\n onResponderGrant: () => {\n dismissing = true;\n },\n onResponderRelease: () => {\n if (!dismissing) return;\n dismissing = false;\n const focused = engine.focused;\n if (focused) engine.dispatchCommand(focused, 'blur');\n },\n onResponderTerminate: () => {\n dismissing = false;\n },\n });\n return () => {\n stopScroll();\n stopResponder();\n };\n}\n"]}
1
+ {"version":3,"file":"keyboard-taps.js","sourceRoot":"","sources":["../src/keyboard-taps.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,UAAU,GAIX,MAAM,mBAAmB,CAAC;AAI3B;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAAkB,EAClB,IAAc,EACd,MAAuC;IAEvC,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,+FAA+F;IAC/F,MAAM,YAAY,GAAG,CAAC,MAAkB,EAAW,EAAE,CACnD,MAAM,CAAC,YAAY,KAAK,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;IAElF,6FAA6F;IAC7F,4EAA4E;IAC5E,MAAM,UAAU,GAAG,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;QACjE,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;QAC9C,gCAAgC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC;QAC7F,yBAAyB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,CAAC;QACxF,gBAAgB,EAAE,GAAG,EAAE;YACrB,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,kBAAkB,EAAE,GAAG,EAAE;YACvB,IAAI,CAAC,UAAU;gBAAE,OAAO;YACxB,UAAU,GAAG,KAAK,CAAC;YACnB,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,CAAC;QACD,oBAAoB,EAAE,GAAG,EAAE;YACzB,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;KACF,CAAC,CAAC;IACH,OAAO,GAAG,EAAE;QACV,UAAU,EAAE,CAAC;QACb,aAAa,EAAE,CAAC;IAClB,CAAC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,MAAkB;IACxC,KAAK,IAAI,EAAE,GAAsB,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,CAAC;QAC5D,IAAI,UAAU,CAAC,EAAE,CAAC,KAAK,oBAAoB;YAAE,OAAO,IAAI,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import {\n isTextInput,\n viewNameOf,\n type EngineNode,\n type HostEngine,\n type HostNode,\n} from '@ng-native/fabric';\n\nexport type KeyboardShouldPersistTaps = 'always' | 'never' | 'handled';\n\n/**\n * What a tap in a scrolling container does while a text input has the keyboard up, which is\n * `ScrollView.js`'s JavaScript in React Native rather than anything native. `never` (the default):\n * a tap anywhere but on a text input is the container's before a child sees it, and all it does is\n * dismiss. `handled`: the child gets first refusal, and the tap dismisses only if nothing took it.\n * `always`: the keyboard stays. A tap on another text input never dismisses, so a form's next\n * field takes the focus with the keyboard still up: React Native's `never` lets any text input's\n * tap through, and with `handled` the text input takes the tap itself.\n *\n * Returns the responder's release, for the container's destroy.\n */\nexport function dismissKeyboardOnTap(\n engine: HostEngine,\n node: HostNode,\n policy: () => KeyboardShouldPersistTaps,\n): () => void {\n let dismissing = false;\n\n /** A keyboard is up for an input, and the tap is not on a text input or above the keyboard. */\n const tapDismisses = (target: EngineNode): boolean =>\n engine.focusedField !== null && !isTextInput(target) && !inAccessoryBar(target);\n\n // RN's `_observedScrollSinceBecomingResponder`: a touch that scrolled was a drag, and a drag\n // through the content is reading it, so its release leaves the keyboard up.\n const stopScroll = engine.setEventListener(node, 'topScroll', () => {\n dismissing = false;\n });\n const stopResponder = engine.setResponder(node, {\n onStartShouldSetResponderCapture: (_, target) => policy() === 'never' && tapDismisses(target),\n onStartShouldSetResponder: (_, target) => policy() === 'handled' && tapDismisses(target),\n onResponderGrant: () => {\n dismissing = true;\n },\n onResponderRelease: () => {\n if (!dismissing) return;\n dismissing = false;\n engine.blurFocused();\n },\n onResponderTerminate: () => {\n dismissing = false;\n },\n });\n return () => {\n stopScroll();\n stopResponder();\n };\n}\n\n/**\n * Whether the tap is on an `input-accessory-view` or inside one. On iOS the bar is drawn above the\n * keyboard, outside the scroll view it is written in, so a tap on it is not a tap outside the field,\n * as UIKit has it. React Native's `ScrollView.js` dismisses the keyboard for it all the same. On\n * Android the element is a plain view, inside the scroll view on screen too.\n */\nfunction inAccessoryBar(target: EngineNode): boolean {\n for (let at: EngineNode | null = target; at; at = at.parent) {\n if (viewNameOf(at) === 'InputAccessoryView') return true;\n }\n return false;\n}\n"]}
@@ -21,7 +21,7 @@
21
21
  * is the same reason it has no header. Give a modal a provider of its own with
22
22
  * `[reportInsets]="false"`, so its geometry stays out of the app-wide `SafeArea` while it is up.
23
23
  */
24
- import { Component, DestroyRef, ElementRef, Renderer2, booleanAttribute, inject, input, } from '@angular/core';
24
+ import { ApplicationRef, Component, DestroyRef, ElementRef, Renderer2, booleanAttribute, inject, input, } from '@angular/core';
25
25
  import { SafeArea } from '@ng-native/device';
26
26
  import { Engine, claimHost } from '@ng-native/fabric';
27
27
  import { registerSafeAreaComponents } from "./safe-area.js";
@@ -47,16 +47,25 @@ export class SafeAreaProvider {
47
47
  claimHost(host);
48
48
  const area = inject(SafeArea);
49
49
  const engine = inject(Engine, { optional: true });
50
+ const app = inject(ApplicationRef);
50
51
  const stop = inject(Renderer2).listen(host, 'insetsChange', (event) => {
51
52
  const change = event?.nativeEvent;
52
53
  if (!change || !this.reportInsets())
53
54
  return;
54
- area.report(change.insets, change.frame);
55
+ area.report(change.insets, change.frame, this);
55
56
  // Also published to the stylesheet, where a padding that clears the home indicator
56
57
  // belongs. `env()` on the web; a seeded custom property here.
57
58
  engine?.updateTokens(insetTokens(change.insets));
58
59
  });
59
- inject(DestroyRef).onDestroy(stop);
60
+ inject(DestroyRef).onDestroy(() => {
61
+ stop();
62
+ // The whole app going takes `SafeArea` and the stylesheet with it: a restyle and a commit
63
+ // of a tree on its way out would be all this did.
64
+ if (app.injector.destroyed)
65
+ return;
66
+ if (area.withdraw(this))
67
+ engine?.updateTokens(insetTokens(area.insets()));
68
+ });
60
69
  }
61
70
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: SafeAreaProvider, deps: [], target: i0.ɵɵFactoryTarget.Component });
62
71
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: SafeAreaProvider, isStandalone: true, selector: "safe-area-provider", inputs: { reportInsets: { classPropertyName: "reportInsets", publicName: "reportInsets", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: '<ng-content />', isInline: true });
@@ -1 +1 @@
1
- {"version":3,"file":"safe-area-provider.js","sourceRoot":"","sources":["../src/safe-area-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,KAAK,GACN,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAA2B,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAiB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;;AAE5D,4FAA4F;AAC5F,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO;QACL,uBAAuB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE;QAC/C,yBAAyB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;QACnD,0BAA0B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QACrD,wBAAwB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KAClD,CAAC;AACJ,CAAC;AAWD,MAAM,OAAO,gBAAgB;IAC3B;;;OAGG;IACM,YAAY,GAAG,KAAK,CAAC,IAAI,oFAAI,SAAS,EAAE,gBAAgB,GAAG,CAAC;IAErE;QACE,0BAA0B,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;QAC1D,SAAS,CAAC,IAAI,CAAC,CAAC;QAEhB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CACnC,IAAI,EACJ,cAAc,EACd,CAAC,KAAqC,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,KAAK,EAAE,WAAW,CAAC;YAClC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;gBAAE,OAAO;YAC5C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACzC,mFAAmF;YACnF,8DAA8D;YAC9D,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC,CACF,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;uGA3BU,gBAAgB;2FAAhB,gBAAgB,uOAFjB,gBAAgB;;2FAEf,gBAAgB;kBAJ5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,oBAAoB;oBAC9B,QAAQ,EAAE,gBAAgB;iBAC3B","sourcesContent":["/**\n * The view that measures the safe area and reports it, for the layouts that need the numbers.\n *\n * ```html\n * <safe-area-provider>\n * <native-stack-outlet />\n * </safe-area-provider>\n * ```\n *\n * One, at the root, wrapping everything. What it measures is its own frame and the insets around\n * it, which is why it has to be the thing filling the window rather than a view somewhere inside\n * it. It fills by default and paints nothing.\n *\n * **A `<safe-area-view>` needs one above it**, which is less obvious than it sounds. The native\n * view walks its superviews for a provider and falls back to reading its own insets - but only\n * once, as it enters the window, before it has been laid out, and nothing notifies it when they\n * change. With no provider it therefore insets by zero and looks like it is working, until a later\n * update to that view happens to re-read them and the content jumps.\n *\n * That is also why a presented screen needs its own: it is outside the stack's view tree, which\n * is the same reason it has no header. Give a modal a provider of its own with\n * `[reportInsets]=\"false\"`, so its geometry stays out of the app-wide `SafeArea` while it is up.\n */\nimport {\n Component,\n DestroyRef,\n ElementRef,\n Renderer2,\n booleanAttribute,\n inject,\n input,\n} from '@angular/core';\nimport { SafeArea, type Frame, type Insets } from '@ng-native/device';\nimport { Engine, claimHost, type HostNode } from '@ng-native/fabric';\nimport { registerSafeAreaComponents } from './safe-area.ts';\n\n/** The insets, in the form the cascade carries: one custom property per edge, in points. */\nfunction insetTokens(insets: Insets): Record<string, { length: number }> {\n return {\n '--safe-area-inset-top': { length: insets.top },\n '--safe-area-inset-right': { length: insets.right },\n '--safe-area-inset-bottom': { length: insets.bottom },\n '--safe-area-inset-left': { length: insets.left },\n };\n}\n\ninterface InsetsChange {\n readonly insets: Insets;\n readonly frame: Frame;\n}\n\n@Component({\n selector: 'safe-area-provider',\n template: '<ng-content />',\n})\nexport class SafeAreaProvider {\n /**\n * Whether what this measures becomes `SafeArea.insets`. The app has one safe area; a second\n * provider inside a modal is there so native descendants can find one, not to redefine it.\n */\n readonly reportInsets = input(true, { transform: booleanAttribute });\n\n constructor() {\n registerSafeAreaComponents();\n const host = inject(ElementRef).nativeElement as HostNode;\n claimHost(host);\n\n const area = inject(SafeArea);\n const engine = inject(Engine, { optional: true });\n const stop = inject(Renderer2).listen(\n host,\n 'insetsChange',\n (event: { nativeEvent?: InsetsChange }) => {\n const change = event?.nativeEvent;\n if (!change || !this.reportInsets()) return;\n area.report(change.insets, change.frame);\n // Also published to the stylesheet, where a padding that clears the home indicator\n // belongs. `env()` on the web; a seeded custom property here.\n engine?.updateTokens(insetTokens(change.insets));\n },\n );\n inject(DestroyRef).onDestroy(stop);\n }\n}\n"]}
1
+ {"version":3,"file":"safe-area-provider.js","sourceRoot":"","sources":["../src/safe-area-provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EACL,cAAc,EACd,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,MAAM,EACN,KAAK,GACN,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,QAAQ,EAA2B,MAAM,mBAAmB,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAiB,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;;AAE5D,4FAA4F;AAC5F,SAAS,WAAW,CAAC,MAAc;IACjC,OAAO;QACL,uBAAuB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,EAAE;QAC/C,yBAAyB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE;QACnD,0BAA0B,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;QACrD,wBAAwB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;KAClD,CAAC;AACJ,CAAC;AAWD,MAAM,OAAO,gBAAgB;IAC3B;;;OAGG;IACM,YAAY,GAAG,KAAK,CAAC,IAAI,oFAAI,SAAS,EAAE,gBAAgB,GAAG,CAAC;IAErE;QACE,0BAA0B,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;QAC1D,SAAS,CAAC,IAAI,CAAC,CAAC;QAEhB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CACnC,IAAI,EACJ,cAAc,EACd,CAAC,KAAqC,EAAE,EAAE;YACxC,MAAM,MAAM,GAAG,KAAK,EAAE,WAAW,CAAC;YAClC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE;gBAAE,OAAO;YAC5C,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;YAC/C,mFAAmF;YACnF,8DAA8D;YAC9D,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QACnD,CAAC,CACF,CAAC;QACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YAChC,IAAI,EAAE,CAAC;YACP,0FAA0F;YAC1F,kDAAkD;YAClD,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS;gBAAE,OAAO;YACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;uGAlCU,gBAAgB;2FAAhB,gBAAgB,uOAFjB,gBAAgB;;2FAEf,gBAAgB;kBAJ5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,oBAAoB;oBAC9B,QAAQ,EAAE,gBAAgB;iBAC3B","sourcesContent":["/**\n * The view that measures the safe area and reports it, for the layouts that need the numbers.\n *\n * ```html\n * <safe-area-provider>\n * <native-stack-outlet />\n * </safe-area-provider>\n * ```\n *\n * One, at the root, wrapping everything. What it measures is its own frame and the insets around\n * it, which is why it has to be the thing filling the window rather than a view somewhere inside\n * it. It fills by default and paints nothing.\n *\n * **A `<safe-area-view>` needs one above it**, which is less obvious than it sounds. The native\n * view walks its superviews for a provider and falls back to reading its own insets - but only\n * once, as it enters the window, before it has been laid out, and nothing notifies it when they\n * change. With no provider it therefore insets by zero and looks like it is working, until a later\n * update to that view happens to re-read them and the content jumps.\n *\n * That is also why a presented screen needs its own: it is outside the stack's view tree, which\n * is the same reason it has no header. Give a modal a provider of its own with\n * `[reportInsets]=\"false\"`, so its geometry stays out of the app-wide `SafeArea` while it is up.\n */\nimport {\n ApplicationRef,\n Component,\n DestroyRef,\n ElementRef,\n Renderer2,\n booleanAttribute,\n inject,\n input,\n} from '@angular/core';\nimport { SafeArea, type Frame, type Insets } from '@ng-native/device';\nimport { Engine, claimHost, type HostNode } from '@ng-native/fabric';\nimport { registerSafeAreaComponents } from './safe-area.ts';\n\n/** The insets, in the form the cascade carries: one custom property per edge, in points. */\nfunction insetTokens(insets: Insets): Record<string, { length: number }> {\n return {\n '--safe-area-inset-top': { length: insets.top },\n '--safe-area-inset-right': { length: insets.right },\n '--safe-area-inset-bottom': { length: insets.bottom },\n '--safe-area-inset-left': { length: insets.left },\n };\n}\n\ninterface InsetsChange {\n readonly insets: Insets;\n readonly frame: Frame;\n}\n\n@Component({\n selector: 'safe-area-provider',\n template: '<ng-content />',\n})\nexport class SafeAreaProvider {\n /**\n * Whether what this measures becomes `SafeArea.insets`. The app has one safe area; a second\n * provider inside a modal is there so native descendants can find one, not to redefine it.\n */\n readonly reportInsets = input(true, { transform: booleanAttribute });\n\n constructor() {\n registerSafeAreaComponents();\n const host = inject(ElementRef).nativeElement as HostNode;\n claimHost(host);\n\n const area = inject(SafeArea);\n const engine = inject(Engine, { optional: true });\n const app = inject(ApplicationRef);\n const stop = inject(Renderer2).listen(\n host,\n 'insetsChange',\n (event: { nativeEvent?: InsetsChange }) => {\n const change = event?.nativeEvent;\n if (!change || !this.reportInsets()) return;\n area.report(change.insets, change.frame, this);\n // Also published to the stylesheet, where a padding that clears the home indicator\n // belongs. `env()` on the web; a seeded custom property here.\n engine?.updateTokens(insetTokens(change.insets));\n },\n );\n inject(DestroyRef).onDestroy(() => {\n stop();\n // The whole app going takes `SafeArea` and the stylesheet with it: a restyle and a commit\n // of a tree on its way out would be all this did.\n if (app.injector.destroyed) return;\n if (area.withdraw(this)) engine?.updateTokens(insetTokens(area.insets()));\n });\n }\n}\n"]}
@@ -25,7 +25,7 @@ export declare abstract class ScrollViewProps extends ViewBase {
25
25
  /** Stop only at these offsets. */
26
26
  readonly snapToOffsets: import("@angular/core").InputSignal<readonly number[] | undefined>;
27
27
  /** Where a snap point sits in the viewport. */
28
- readonly snapToAlignment: import("@angular/core").InputSignal<"center" | "start" | "end" | undefined>;
28
+ readonly snapToAlignment: import("@angular/core").InputSignal<"center" | "end" | "start" | undefined>;
29
29
  /** Whether the start of the content is a snap point. Defaults to true. */
30
30
  readonly snapToStart: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
31
31
  /** Whether the end of the content is a snap point. Defaults to true. */
@@ -0,0 +1,20 @@
1
+ import { type AnimatedValue, type CompositeAnimation, type SpringConfig, type TimingConfig } from '../animated.ts';
2
+ import { AnimatedStyleBase } from '../animated-style.ts';
3
+ import * as i0 from "@angular/core";
4
+ export { Easing } from '../easing.ts';
5
+ export declare const Animated: {
6
+ timing: (value: AnimatedValue, config: TimingConfig) => CompositeAnimation;
7
+ spring: (value: AnimatedValue, config: SpringConfig) => CompositeAnimation;
8
+ delay: (_time: number) => CompositeAnimation;
9
+ Value: typeof AnimatedValue;
10
+ Node: typeof import("../animated.ts").AnimatedNode;
11
+ Interpolation: typeof import("../animated.ts").AnimatedInterpolation;
12
+ sequence: (animations: readonly CompositeAnimation[]) => CompositeAnimation;
13
+ parallel: (animations: readonly CompositeAnimation[], config?: {
14
+ stopTogether?: boolean;
15
+ }) => CompositeAnimation;
16
+ };
17
+ export declare class AnimatedStyle extends AnimatedStyleBase {
18
+ static ɵfac: i0.ɵɵFactoryDeclaration<AnimatedStyle, never>;
19
+ static ɵdir: i0.ɵɵDirectiveDeclaration<AnimatedStyle, "[animatedStyle]", never, {}, {}, never, never, true, never>;
20
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `@ng-native/components/animations` where React Native's animation graph cannot load: a test,
3
+ * which `ngNative()` resolves the entry point from to here.
4
+ *
5
+ * The real entry point reaches `react-native/Libraries/Animated`, which is Flow source that Node
6
+ * cannot parse. This is the browser build's graph (`animated.ts`) with no frames between: an
7
+ * animation finishes the moment it starts, its value is where it ends, and its callback is called
8
+ * with `finished: true` at once, so a test reads the style an animation lands on. `timing`,
9
+ * `spring` and `delay` are instant; `sequence` and `parallel` compose them as they always do.
10
+ */
11
+ import { Directive } from '@angular/core';
12
+ import { Animated as graph, animationBackend, } from "../animated.js";
13
+ import { AnimatedStyleBase } from "../animated-style.js";
14
+ import { ANIMATION } from "../animation.js";
15
+ import * as i0 from "@angular/core";
16
+ export { Easing } from "../easing.js";
17
+ /** A zero duration with no delay is the one timing the graph ends without asking for a frame. */
18
+ const instant = (toValue) => ({ toValue, duration: 0, delay: 0 });
19
+ export const Animated = {
20
+ ...graph,
21
+ timing: (value, config) => graph.timing(value, instant(config.toValue)),
22
+ spring: (value, config) => graph.timing(value, instant(config.toValue)),
23
+ delay: (_time) => graph.delay(0),
24
+ };
25
+ export class AnimatedStyle extends AnimatedStyleBase {
26
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: AnimatedStyle, deps: null, target: i0.ɵɵFactoryTarget.Directive });
27
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.2.0", type: AnimatedStyle, isStandalone: true, selector: "[animatedStyle]", providers: [{ provide: ANIMATION, useValue: animationBackend }], usesInheritance: true, ngImport: i0 });
28
+ }
29
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: AnimatedStyle, decorators: [{
30
+ type: Directive,
31
+ args: [{
32
+ selector: '[animatedStyle]',
33
+ providers: [{ provide: ANIMATION, useValue: animationBackend }],
34
+ }]
35
+ }] });
36
+ //# sourceMappingURL=animations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animations.js","sourceRoot":"","sources":["../../src/stand-ins/animations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EACL,QAAQ,IAAI,KAAK,EACjB,gBAAgB,GAKjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,iGAAiG;AACjG,MAAM,OAAO,GAAG,CAAC,OAAe,EAAgB,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;AAExF,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,GAAG,KAAK;IACR,MAAM,EAAE,CAAC,KAAoB,EAAE,MAAoB,EAAsB,EAAE,CACzE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,KAAoB,EAAE,MAAoB,EAAsB,EAAE,CACzE,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,EAAE,CAAC,KAAa,EAAsB,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;CAC7D,CAAC;AAMF,MAAM,OAAO,aAAc,SAAQ,iBAAiB;uGAAvC,aAAa;2FAAb,aAAa,8DAFb,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;;2FAEpD,aAAa;kBAJzB,SAAS;mBAAC;oBACT,QAAQ,EAAE,iBAAiB;oBAC3B,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,gBAAgB,EAAE,CAAC;iBAChE","sourcesContent":["/**\n * `@ng-native/components/animations` where React Native's animation graph cannot load: a test,\n * which `ngNative()` resolves the entry point from to here.\n *\n * The real entry point reaches `react-native/Libraries/Animated`, which is Flow source that Node\n * cannot parse. This is the browser build's graph (`animated.ts`) with no frames between: an\n * animation finishes the moment it starts, its value is where it ends, and its callback is called\n * with `finished: true` at once, so a test reads the style an animation lands on. `timing`,\n * `spring` and `delay` are instant; `sequence` and `parallel` compose them as they always do.\n */\nimport { Directive } from '@angular/core';\nimport {\n Animated as graph,\n animationBackend,\n type AnimatedValue,\n type CompositeAnimation,\n type SpringConfig,\n type TimingConfig,\n} from '../animated.ts';\nimport { AnimatedStyleBase } from '../animated-style.ts';\nimport { ANIMATION } from '../animation.ts';\n\nexport { Easing } from '../easing.ts';\n\n/** A zero duration with no delay is the one timing the graph ends without asking for a frame. */\nconst instant = (toValue: number): TimingConfig => ({ toValue, duration: 0, delay: 0 });\n\nexport const Animated = {\n ...graph,\n timing: (value: AnimatedValue, config: TimingConfig): CompositeAnimation =>\n graph.timing(value, instant(config.toValue)),\n spring: (value: AnimatedValue, config: SpringConfig): CompositeAnimation =>\n graph.timing(value, instant(config.toValue)),\n delay: (_time: number): CompositeAnimation => graph.delay(0),\n};\n\n@Directive({\n selector: '[animatedStyle]',\n providers: [{ provide: ANIMATION, useValue: animationBackend }],\n})\nexport class AnimatedStyle extends AnimatedStyleBase {}\n"]}