@ng-native/components 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,7 @@ export class AnimatedStyleBase {
32
32
  node = inject(ElementRef).nativeElement;
33
33
  // The shared host seam, read off the node as every primitive reads it, so a browser host serves
34
34
  // this as well as Fabric does. Only the react tag is Fabric's own, and `connect()` asks for it.
35
- engine = this.node.host ?? inject(HostEngine);
35
+ engine = this.node.engine ?? inject(HostEngine);
36
36
  animation = inject(ANIMATION);
37
37
  /** A style object whose values may be animated nodes, plain numbers, or a mix. */
38
38
  animatedStyle = input.required(/* @ts-ignore */
@@ -1 +1 @@
1
- {"version":3,"file":"animated-style.js","sourceRoot":"","sources":["../src/animated-style.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Binds a style holding animated values to the element it sits on: the Angular equivalent of\n * React Native's `createAnimatedComponent`.\n *\n * ```html\n * <view [animatedStyle]=\"{ opacity: fade, transform: [{ translateY: slide }] }\">\n * ```\n *\n * Two drivers, and the difference is where the work happens. With `useNativeDriver: true` the\n * whole animation runs on the UI thread: this hands the graph the element's react tag once and\n * then does nothing per frame, so the animation keeps its frame rate with the JavaScript thread\n * blocked. Without it, every frame calls back here and is written to the node. A browser has only\n * the second kind: `animations-web.ts` supplies a graph that is JavaScript-driven throughout, and\n * `useNativeDriver` there changes nothing.\n *\n * A JavaScript frame commits outside change detection, one of the two exceptions to committing at\n * most once per change-detection pass (the other is a CSS transition). It has to: the graph advances on its own schedule, and waiting for the next change\n * detection pass would drop the frame. The commit is incremental like any other, and touches\n * only the nodes the frame changed.\n */\nimport {\n Directive,\n ElementRef,\n afterNextRender,\n effect,\n inject,\n input,\n untracked,\n} from '@angular/core';\nimport { Engine, HostEngine, type EngineNode, type HostNode } from '@ng-native/fabric';\nimport { ANIMATION, type AnimatedPropsHandle } from './animation.ts';\n\n/**\n * The behaviour, with the graph left as a dependency so it can be driven by a fake. `AnimatedStyle`\n * in `../animations.ts` is the one apps import: same selector, and it supplies the real graph\n * itself - React Native's on a device, `animated.ts` in a browser - so importing the directive is\n * the whole setup.\n */\n@Directive({ selector: '[animatedStyle]' })\nexport class AnimatedStyleBase {\n private readonly node = inject(ElementRef).nativeElement as HostNode;\n // The shared host seam, read off the node as every primitive reads it, so a browser host serves\n // this as well as Fabric does. Only the react tag is Fabric's own, and `connect()` asks for it.\n private readonly engine = this.node.host ?? inject(HostEngine);\n private readonly animation = inject(ANIMATION);\n\n /** A style object whose values may be animated nodes, plain numbers, or a mix. */\n readonly animatedStyle = input.required<Record<string, unknown>>();\n\n private handle: AnimatedPropsHandle | null = null;\n private connected = false;\n\n constructor() {\n // A node has a react tag only once it has been committed, and a native-driven animation\n // never calls back into JavaScript, so there would otherwise be no moment to hand it over.\n afterNextRender(() => this.connect());\n\n effect((onCleanup) => {\n const style = this.animatedStyle();\n const handle = this.animation.props(style, () => untracked(() => this.frame()));\n this.handle = handle;\n this.connected = false;\n handle.attach();\n // The starting values, so the element is right before anything animates. This runs inside\n // change detection, so the commit it needs is the ordinary one.\n this.write(handle.read());\n\n onCleanup(() => {\n handle.detach();\n if (this.handle === handle) this.handle = null;\n });\n });\n }\n\n /**\n * One JavaScript-driven frame. Also the first chance to hand over the react tag: a node has\n * one only after it has been committed, and this runs after at least one commit has happened.\n */\n private frame(): void {\n const handle = this.handle;\n if (!handle) return;\n this.write(handle.read());\n this.engine.commit();\n }\n\n private write(values: Record<string, unknown>): void {\n const current = (this.node.props['style'] as Record<string, unknown> | undefined) ?? {};\n this.engine.setProp(this.node, 'style', { ...current, ...values });\n this.connect();\n }\n\n /**\n * Hand the graph the react tag, once there is one. Calling this before an animation starts is\n * the point: React Native connects the view now if the graph is already native, and remembers\n * it for the moment an animation with the native driver takes over.\n */\n private connect(): void {\n if (this.connected) return;\n // A browser has no native driver to hand a view to, so there is no tag to look for.\n if (!(this.engine instanceof Engine)) return;\n const tag = this.engine.tagOf(this.node as EngineNode);\n if (tag === null) return;\n this.handle?.connect(tag);\n this.connected = true;\n }\n}\n"]}
1
+ {"version":3,"file":"animated-style.js","sourceRoot":"","sources":["../src/animated-style.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Binds a style holding animated values to the element it sits on: the Angular equivalent of\n * React Native's `createAnimatedComponent`.\n *\n * ```html\n * <view [animatedStyle]=\"{ opacity: fade, transform: [{ translateY: slide }] }\">\n * ```\n *\n * Two drivers, and the difference is where the work happens. With `useNativeDriver: true` the\n * whole animation runs on the UI thread: this hands the graph the element's react tag once and\n * then does nothing per frame, so the animation keeps its frame rate with the JavaScript thread\n * blocked. Without it, every frame calls back here and is written to the node. A browser has only\n * the second kind: `animations-web.ts` supplies a graph that is JavaScript-driven throughout, and\n * `useNativeDriver` there changes nothing.\n *\n * A JavaScript frame commits outside change detection, one of the two exceptions to committing at\n * most once per change-detection pass (the other is a CSS transition). It has to: the graph advances on its own schedule, and waiting for the next change\n * detection pass would drop the frame. The commit is incremental like any other, and touches\n * only the nodes the frame changed.\n */\nimport {\n Directive,\n ElementRef,\n afterNextRender,\n effect,\n inject,\n input,\n untracked,\n} from '@angular/core';\nimport { Engine, HostEngine, type EngineNode, type HostNode } from '@ng-native/fabric';\nimport { ANIMATION, type AnimatedPropsHandle } from './animation.ts';\n\n/**\n * The behaviour, with the graph left as a dependency so it can be driven by a fake. `AnimatedStyle`\n * in `../animations.ts` is the one apps import: same selector, and it supplies the real graph\n * itself - React Native's on a device, `animated.ts` in a browser - so importing the directive is\n * the whole setup.\n */\n@Directive({ selector: '[animatedStyle]' })\nexport class AnimatedStyleBase {\n private readonly node = inject(ElementRef).nativeElement as HostNode;\n // The shared host seam, read off the node as every primitive reads it, so a browser host serves\n // this as well as Fabric does. Only the react tag is Fabric's own, and `connect()` asks for it.\n private readonly engine = this.node.engine ?? inject(HostEngine);\n private readonly animation = inject(ANIMATION);\n\n /** A style object whose values may be animated nodes, plain numbers, or a mix. */\n readonly animatedStyle = input.required<Record<string, unknown>>();\n\n private handle: AnimatedPropsHandle | null = null;\n private connected = false;\n\n constructor() {\n // A node has a react tag only once it has been committed, and a native-driven animation\n // never calls back into JavaScript, so there would otherwise be no moment to hand it over.\n afterNextRender(() => this.connect());\n\n effect((onCleanup) => {\n const style = this.animatedStyle();\n const handle = this.animation.props(style, () => untracked(() => this.frame()));\n this.handle = handle;\n this.connected = false;\n handle.attach();\n // The starting values, so the element is right before anything animates. This runs inside\n // change detection, so the commit it needs is the ordinary one.\n this.write(handle.read());\n\n onCleanup(() => {\n handle.detach();\n if (this.handle === handle) this.handle = null;\n });\n });\n }\n\n /**\n * One JavaScript-driven frame. Also the first chance to hand over the react tag: a node has\n * one only after it has been committed, and this runs after at least one commit has happened.\n */\n private frame(): void {\n const handle = this.handle;\n if (!handle) return;\n this.write(handle.read());\n this.engine.commit();\n }\n\n private write(values: Record<string, unknown>): void {\n const current = (this.node.props['style'] as Record<string, unknown> | undefined) ?? {};\n this.engine.setProp(this.node, 'style', { ...current, ...values });\n this.connect();\n }\n\n /**\n * Hand the graph the react tag, once there is one. Calling this before an animation starts is\n * the point: React Native connects the view now if the graph is already native, and remembers\n * it for the moment an animation with the native driver takes over.\n */\n private connect(): void {\n if (this.connected) return;\n // A browser has no native driver to hand a view to, so there is no tag to look for.\n if (!(this.engine instanceof Engine)) return;\n const tag = this.engine.tagOf(this.node as EngineNode);\n if (tag === null) return;\n this.handle?.connect(tag);\n this.connected = true;\n }\n}\n"]}
@@ -18,7 +18,7 @@ export class ContentContainer {
18
18
  /** The app's classes for the view. */
19
19
  classes = input(undefined, { ...(ngDevMode ? { debugName: "classes" } : /* istanbul ignore next */ {}), alias: 'contentContainerClass' });
20
20
  node = inject(ElementRef).nativeElement;
21
- engine = this.node.host ?? inject(HostEngine);
21
+ engine = this.node.engine ?? inject(HostEngine);
22
22
  ngOnChanges() {
23
23
  this.engine.adoptScope(this.node, this.classes() ? this.owner() : null);
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"content-container.js","sourceRoot":"","sources":["../src/content-container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAkB,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,UAAU,EAAiB,MAAM,mBAAmB,CAAC;;AAE9D;;;;;;;;;;GAUG;AAKH,MAAM,OAAO,gBAAgB;IAC3B,6EAA6E;IACpE,KAAK,GAAG,KAAK,CAAC,QAAQ,4EAAa,KAAK,EAAE,oBAAoB,GAAG,CAAC;IAC3E,sCAAsC;IAC7B,OAAO,GAAG,KAAK,CAAS,SAAS,+EAAI,KAAK,EAAE,uBAAuB,GAAG,CAAC;IAE/D,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;IACpD,MAAM,GAAe,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC;IAE3E,WAAW;QACT,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1E,CAAC;uGAXU,gBAAgB;2FAAhB,gBAAgB;;2FAAhB,gBAAgB;kBAJ5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,sBAAsB;oBAChC,IAAI,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE;iBACjC","sourcesContent":["import { Directive, ElementRef, type OnChanges, inject, input } from '@angular/core';\nimport { HostEngine, type HostNode } from '@ng-native/fabric';\n\n/**\n * The view a component makes to hold its children, styled by the app through\n * `contentContainerClass`: `<scroll-view>`'s content view, and the view `<keyboard-avoiding-view>`\n * moves.\n *\n * The view is written in the component's template, so on its own it matches only that\n * component's rules and the global sheet. The class is the app's, so while there is one the view\n * is matched against the rules of the template the component is written in, and against its own\n * again once the class is gone. Not before: a type selector in the app's styles would otherwise\n * reach a view that nobody there wrote.\n */\n@Directive({\n selector: '[contentContainerOf]',\n host: { '[class]': 'classes()' },\n})\nexport class ContentContainer implements OnChanges {\n /** The component the view belongs to, whose template the app wrote it in. */\n readonly owner = input.required<HostNode>({ alias: 'contentContainerOf' });\n /** The app's classes for the view. */\n readonly classes = input<string>(undefined, { alias: 'contentContainerClass' });\n\n private readonly node = inject(ElementRef).nativeElement as HostNode;\n private readonly engine: HostEngine = this.node.host ?? inject(HostEngine);\n\n ngOnChanges(): void {\n this.engine.adoptScope(this.node, this.classes() ? this.owner() : null);\n }\n}\n"]}
1
+ {"version":3,"file":"content-container.js","sourceRoot":"","sources":["../src/content-container.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAkB,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,UAAU,EAAiB,MAAM,mBAAmB,CAAC;;AAE9D;;;;;;;;;;GAUG;AAKH,MAAM,OAAO,gBAAgB;IAC3B,6EAA6E;IACpE,KAAK,GAAG,KAAK,CAAC,QAAQ,4EAAa,KAAK,EAAE,oBAAoB,GAAG,CAAC;IAC3E,sCAAsC;IAC7B,OAAO,GAAG,KAAK,CAAS,SAAS,+EAAI,KAAK,EAAE,uBAAuB,GAAG,CAAC;IAE/D,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,aAAyB,CAAC;IACpD,MAAM,GAAe,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,UAAU,CAAC,CAAC;IAE7E,WAAW;QACT,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1E,CAAC;uGAXU,gBAAgB;2FAAhB,gBAAgB;;2FAAhB,gBAAgB;kBAJ5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,sBAAsB;oBAChC,IAAI,EAAE,EAAE,SAAS,EAAE,WAAW,EAAE;iBACjC","sourcesContent":["import { Directive, ElementRef, type OnChanges, inject, input } from '@angular/core';\nimport { HostEngine, type HostNode } from '@ng-native/fabric';\n\n/**\n * The view a component makes to hold its children, styled by the app through\n * `contentContainerClass`: `<scroll-view>`'s content view, and the view `<keyboard-avoiding-view>`\n * moves.\n *\n * The view is written in the component's template, so on its own it matches only that\n * component's rules and the global sheet. The class is the app's, so while there is one the view\n * is matched against the rules of the template the component is written in, and against its own\n * again once the class is gone. Not before: a type selector in the app's styles would otherwise\n * reach a view that nobody there wrote.\n */\n@Directive({\n selector: '[contentContainerOf]',\n host: { '[class]': 'classes()' },\n})\nexport class ContentContainer implements OnChanges {\n /** The component the view belongs to, whose template the app wrote it in. */\n readonly owner = input.required<HostNode>({ alias: 'contentContainerOf' });\n /** The app's classes for the view. */\n readonly classes = input<string>(undefined, { alias: 'contentContainerClass' });\n\n private readonly node = inject(ElementRef).nativeElement as HostNode;\n private readonly engine: HostEngine = this.node.engine ?? inject(HostEngine);\n\n ngOnChanges(): void {\n this.engine.adoptScope(this.node, this.classes() ? this.owner() : null);\n }\n}\n"]}
package/dist/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"]}
@@ -37,7 +37,7 @@ import { View } from "./view.js";
37
37
  import { ViewBase } from "./view-base.js";
38
38
  import * as i0 from "@angular/core";
39
39
  /** The characters under a node, in order. */
40
- const textOf = (node) => node.kind === 'text' ? node.text : node.children.map(textOf).join('');
40
+ const textOf = (node) => node.nodeKind === 'text' ? node.nodeText : node.childList.map(textOf).join('');
41
41
  let registered = false;
42
42
  /** Teach the engine the element, once, before its first commit. */
43
43
  function registerGradientText() {
@@ -1 +1 @@
1
- {"version":3,"file":"gradient-text.js","sourceRoot":"","sources":["../src/gradient-text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Text filled with a gradient, or with any background: the background is drawn through the shapes\n * of the letters.\n *\n * ```html\n * <gradient-text class=\"brand\">Week</gradient-text>\n * ```\n *\n * ```css\n * .brand {\n * background-image: linear-gradient(to bottom, #437dfc, #4ad0ef);\n * background-clip: text;\n * color: transparent;\n * font-size: 34px;\n * font-weight: 700;\n * }\n * ```\n *\n * The rule is the one a web page writes. `background-clip: text` is what this element does, so a\n * rule that has it applies to `<gradient-text>` and to nothing else: on a `<text>` it would leave\n * transparent letters on a block of colour. The `color` is not used, since the letters are the\n * mask and are drawn in full.\n *\n * It is text like any other: it wraps, follows the device's text size, takes `<text>` spans\n * inside it with their own weight or style, and is sized by its letters. What a span cannot have\n * here is a colour of its own, because the fill is the one background behind all of it.\n *\n * The app must have `@react-native-masked-view/masked-view` installed, which is where the native\n * view comes from (`npx expo install @react-native-masked-view/masked-view`); none of its\n * JavaScript is imported. The view masks what is inside it by its first child, so the letters are\n * that child and the background is painted on a view behind them.\n */\nimport {\n Component,\n ElementRef,\n viewChild,\n type AfterContentChecked,\n type AfterViewInit,\n} from '@angular/core';\nimport { registerViewName, type Engine, type EngineNode } from '@ng-native/fabric';\nimport { Text } from './text.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/** The characters under a node, in order. */\nconst textOf = (node: EngineNode): string =>\n node.kind === 'text' ? node.text : node.children.map(textOf).join('');\n\nlet registered = false;\n\n/** Teach the engine the element, once, before its first commit. */\nfunction registerGradientText(): void {\n if (registered) return;\n registered = true;\n registerViewName('gradient-text', 'RNCMaskedView');\n}\n\n@Component({\n selector: 'gradient-text',\n imports: [Text, View],\n template: `\n <text #letters class=\"letters\"><ng-content /></text>\n <view #fill class=\"fill\" pointerEvents=\"none\"></view>\n `,\n styles: `\n .letters {\n color: black;\n }\n .fill {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n `,\n})\nexport class GradientText extends ViewBase implements AfterViewInit, AfterContentChecked {\n private readonly fill = viewChild.required('fill', { read: ElementRef });\n private readonly letters = viewChild('letters', { read: ElementRef });\n /** The label last written from the letters, to write it again only when they change. */\n private spoken: string | undefined;\n\n constructor() {\n super();\n registerGradientText();\n // One element to a screen reader, as a text is. An input of the same name is written after.\n this.engine.setProp(this.node, 'accessible', true);\n this.engine.setProp(this.node, 'accessibilityRole', 'text');\n }\n\n ngAfterViewInit(): void {\n this.paintOnFill();\n this.labelFromLetters();\n }\n\n ngAfterContentChecked(): void {\n this.labelFromLetters();\n }\n\n /**\n * Say the letters. iOS takes the mask out of the view hierarchy, so there is no text under the\n * element for VoiceOver to read, and an element with nothing to say is not found at all. A\n * label the app gave is left as it is.\n */\n private labelFromLetters(): void {\n const letters = this.letters()?.nativeElement as EngineNode | undefined;\n const given = this.accessibilityLabel() ?? this.ariaLabel();\n if (!letters || given !== undefined) return;\n const label = textOf(letters).replace(/\\s+/g, ' ').trim();\n if (label === this.spoken) return;\n this.spoken = label;\n this.engine.setProp(this.node, 'accessibilityLabel', label || null);\n }\n\n /**\n * The host's background is the fill's to paint: the view masks its children and not itself.\n * Before the first commit, so the background is never drawn unmasked.\n */\n private paintOnFill(): void {\n const engine = this.engine as Partial<Pick<Engine, 'paintOn'>>;\n engine.paintOn?.(this.node as EngineNode, this.fill().nativeElement as EngineNode);\n }\n}\n"]}
1
+ {"version":3,"file":"gradient-text.js","sourceRoot":"","sources":["../src/gradient-text.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Text filled with a gradient, or with any background: the background is drawn through the shapes\n * of the letters.\n *\n * ```html\n * <gradient-text class=\"brand\">Week</gradient-text>\n * ```\n *\n * ```css\n * .brand {\n * background-image: linear-gradient(to bottom, #437dfc, #4ad0ef);\n * background-clip: text;\n * color: transparent;\n * font-size: 34px;\n * font-weight: 700;\n * }\n * ```\n *\n * The rule is the one a web page writes. `background-clip: text` is what this element does, so a\n * rule that has it applies to `<gradient-text>` and to nothing else: on a `<text>` it would leave\n * transparent letters on a block of colour. The `color` is not used, since the letters are the\n * mask and are drawn in full.\n *\n * It is text like any other: it wraps, follows the device's text size, takes `<text>` spans\n * inside it with their own weight or style, and is sized by its letters. What a span cannot have\n * here is a colour of its own, because the fill is the one background behind all of it.\n *\n * The app must have `@react-native-masked-view/masked-view` installed, which is where the native\n * view comes from (`npx expo install @react-native-masked-view/masked-view`); none of its\n * JavaScript is imported. The view masks what is inside it by its first child, so the letters are\n * that child and the background is painted on a view behind them.\n */\nimport {\n Component,\n ElementRef,\n viewChild,\n type AfterContentChecked,\n type AfterViewInit,\n} from '@angular/core';\nimport { registerViewName, type Engine, type EngineNode } from '@ng-native/fabric';\nimport { Text } from './text.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\n\n/** The characters under a node, in order. */\nconst textOf = (node: EngineNode): string =>\n node.nodeKind === 'text' ? node.nodeText : node.childList.map(textOf).join('');\n\nlet registered = false;\n\n/** Teach the engine the element, once, before its first commit. */\nfunction registerGradientText(): void {\n if (registered) return;\n registered = true;\n registerViewName('gradient-text', 'RNCMaskedView');\n}\n\n@Component({\n selector: 'gradient-text',\n imports: [Text, View],\n template: `\n <text #letters class=\"letters\"><ng-content /></text>\n <view #fill class=\"fill\" pointerEvents=\"none\"></view>\n `,\n styles: `\n .letters {\n color: black;\n }\n .fill {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n `,\n})\nexport class GradientText extends ViewBase implements AfterViewInit, AfterContentChecked {\n private readonly fill = viewChild.required('fill', { read: ElementRef });\n private readonly letters = viewChild('letters', { read: ElementRef });\n /** The label last written from the letters, to write it again only when they change. */\n private spoken: string | undefined;\n\n constructor() {\n super();\n registerGradientText();\n // One element to a screen reader, as a text is. An input of the same name is written after.\n this.engine.setProp(this.node, 'accessible', true);\n this.engine.setProp(this.node, 'accessibilityRole', 'text');\n }\n\n ngAfterViewInit(): void {\n this.paintOnFill();\n this.labelFromLetters();\n }\n\n ngAfterContentChecked(): void {\n this.labelFromLetters();\n }\n\n /**\n * Say the letters. iOS takes the mask out of the view hierarchy, so there is no text under the\n * element for VoiceOver to read, and an element with nothing to say is not found at all. A\n * label the app gave is left as it is.\n */\n private labelFromLetters(): void {\n const letters = this.letters()?.nativeElement as EngineNode | undefined;\n const given = this.accessibilityLabel() ?? this.ariaLabel();\n if (!letters || given !== undefined) return;\n const label = textOf(letters).replace(/\\s+/g, ' ').trim();\n if (label === this.spoken) return;\n this.spoken = label;\n this.engine.setProp(this.node, 'accessibilityLabel', label || null);\n }\n\n /**\n * The host's background is the fill's to paint: the view masks its children and not itself.\n * Before the first commit, so the background is never drawn unmasked.\n */\n private paintOnFill(): void {\n const engine = this.engine as Partial<Pick<Engine, 'paintOn'>>;\n engine.paintOn?.(this.node as EngineNode, this.fill().nativeElement as EngineNode);\n }\n}\n"]}
@@ -23,7 +23,9 @@ export type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';
23
23
  * The adjustment itself animates with the keyboard's own motion, the way RN's own
24
24
  * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next
25
25
  * layout animation before the style change that moves this view, so the two run together rather
26
- * than the padding, height or position jumping to its resting place in one step.
26
+ * than the padding, height or position jumping to its resting place in one step. Android animates
27
+ * no layout unless an app turns that on, and reports no duration, so there the view jumps; with
28
+ * `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.
27
29
  */
28
30
  export declare class KeyboardAvoidingView extends ViewBase {
29
31
  /**
@@ -45,6 +47,8 @@ export declare class KeyboardAvoidingView extends ViewBase {
45
47
  * header's height; here the view is measured in the window, so a navigation bar needs none.
46
48
  */
47
49
  readonly keyboardVerticalOffset: import("@angular/core").InputSignalWithTransform<number, unknown>;
50
+ private readonly keyboardService;
51
+ /** Where the keyboard is, at every frame of its motion where the platform says. */
48
52
  private readonly keyboard;
49
53
  private readonly layoutAnimation;
50
54
  private readonly frame;
@@ -41,7 +41,9 @@ function layoutEasingOf(easing) {
41
41
  * The adjustment itself animates with the keyboard's own motion, the way RN's own
42
42
  * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next
43
43
  * layout animation before the style change that moves this view, so the two run together rather
44
- * than the padding, height or position jumping to its resting place in one step.
44
+ * than the padding, height or position jumping to its resting place in one step. Android animates
45
+ * no layout unless an app turns that on, and reports no duration, so there the view jumps; with
46
+ * `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.
45
47
  */
46
48
  export class KeyboardAvoidingView extends ViewBase {
47
49
  /**
@@ -66,7 +68,9 @@ export class KeyboardAvoidingView extends ViewBase {
66
68
  * header's height; here the view is measured in the window, so a navigation bar needs none.
67
69
  */
68
70
  keyboardVerticalOffset = input(0, { ...(ngDevMode ? { debugName: "keyboardVerticalOffset" } : /* istanbul ignore next */ {}), transform: numberOr(0) });
69
- keyboard = inject(Keyboard).metrics;
71
+ keyboardService = inject(Keyboard);
72
+ /** Where the keyboard is, at every frame of its motion where the platform says. */
73
+ keyboard = this.keyboardService.position;
70
74
  layoutAnimation = inject(LayoutAnimation);
71
75
  frame = signal(null, /* @ts-ignore */
72
76
  ...(ngDevMode ? [{ debugName: "frame" }] : /* istanbul ignore next */ []));
@@ -116,13 +120,14 @@ export class KeyboardAvoidingView extends ViewBase {
116
120
  ...(ngDevMode ? [{ debugName: "positioned" }] : /* istanbul ignore next */ []));
117
121
  constructor() {
118
122
  super();
119
- // A duration of zero is Android's own keyboard event, which animates itself; there is
120
- // nothing to configure alongside it. `appear`/`leave` are turned off because this is an
121
- // update to a view already on screen, not one appearing or leaving - RN's own bare
122
- // `LayoutAnimation.configureNext({duration, update})` animates nothing else either.
123
+ // A duration of zero is Android's own keyboard event, which says nothing of the motion; there
124
+ // is nothing to configure alongside it. Nor where the view follows the motion frame by frame.
125
+ // `appear`/`leave` are turned off because this is an update to a view already on screen, not
126
+ // one appearing or leaving - RN's own bare `LayoutAnimation.configureNext({duration, update})`
127
+ // animates nothing else either.
123
128
  effect(() => {
124
- const { duration, easing } = this.keyboard();
125
- if (!this.enabled() || !duration)
129
+ const { duration, easing } = this.keyboardService.metrics();
130
+ if (!this.enabled() || !duration || this.keyboardService.followsMotion)
126
131
  return;
127
132
  void this.layoutAnimation.animate(() => { }, {
128
133
  duration,
@@ -1 +1 @@
1
- {"version":3,"file":"keyboard-avoiding-view.js","sourceRoot":"","sources":["../src/keyboard-avoiding-view.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n effect,\n inject,\n input,\n signal,\n booleanAttribute,\n} from '@angular/core';\nimport { Keyboard, LayoutAnimation, type LayoutEasing } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport type { Rect } from './events.ts';\nimport { ContentContainer } from './content-container.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\nimport { numberOr } from './transforms.ts';\n\nexport type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';\n\n/**\n * The names `LayoutEasing` knows. `'keyboard'` is among them: iOS reports its keyboard's own\n * curve by that name, and RN's KeyboardAvoidingView animates on it.\n */\nconst LAYOUT_EASINGS: ReadonlySet<string> = new Set([\n 'linear',\n 'easeInEaseOut',\n 'easeIn',\n 'easeOut',\n 'spring',\n 'keyboard',\n]);\n\n/** A keyboard event's `easing`, narrowed to what `LayoutAnimation` accepts, or left to its default. */\nfunction layoutEasingOf(easing: string | undefined): LayoutEasing | undefined {\n return easing !== undefined && LAYOUT_EASINGS.has(easing) ? (easing as LayoutEasing) : undefined;\n}\n\n/**\n * Moves its content clear of the keyboard.\n *\n * RN's KeyboardAvoidingView is a JS composite: there is no native component, only a view whose\n * padding, height or position tracks the keyboard. Off a device the `Keyboard` service reports\n * nothing, so this renders as a plain view and does nothing, which is also the right behaviour on\n * a platform with no soft keyboard.\n *\n * The overlap is the view's own frame against the keyboard's top edge, so a view that does not\n * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is\n * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in\n * the parent's coordinates, and on a screen under a navigation bar those start a bar's height\n * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by\n * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the\n * view's own height either: padding a view by more than it has pushes its content out of it.\n * When the keyboard source reports no `screenY`, the keyboard's height stands in.\n *\n * The adjustment itself animates with the keyboard's own motion, the way RN's own\n * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next\n * layout animation before the style change that moves this view, so the two run together rather\n * than the padding, height or position jumping to its resting place in one step.\n */\n@Component({\n selector: 'keyboard-avoiding-view',\n imports: [ContentContainer, View],\n template: `\n @if (behavior() === 'position') {\n <view\n [contentContainerOf]=\"node\"\n [contentContainerClass]=\"contentContainerClass()\"\n [style]=\"positioned()\"\n ><ng-content\n /></view>\n } @else {\n <ng-content />\n }\n `,\n host: {\n '[styleOverride]': 'avoidance()',\n '(layout)': 'onLayout($event)',\n },\n})\nexport class KeyboardAvoidingView extends ViewBase {\n /**\n * How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this\n * unset and does nothing; `padding` is the default here because that is what a form wants.\n */\n readonly behavior = input<KeyboardAvoidingBehavior>('padding');\n /** Styles for the inner view that `position` moves. */\n readonly contentContainerStyle = input<Record<string, unknown>>();\n /**\n * Classes for the inner view that `position` moves, matched as if it were written in the\n * template this view is. See `ContentContainer`.\n */\n readonly contentContainerClass = input<string>();\n /** Set to false to stop avoiding without removing the view. */\n readonly enabled = input(true, { transform: booleanAttribute });\n /**\n * Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a\n * header's height; here the view is measured in the window, so a navigation bar needs none.\n */\n readonly keyboardVerticalOffset = input(0, { transform: numberOr(0) });\n\n private readonly keyboard = inject(Keyboard).metrics;\n private readonly layoutAnimation = inject(LayoutAnimation);\n private readonly frame = signal<Rect | null>(null);\n /** The height before any adjustment, which `height` mode shrinks from. */\n private initialHeight: number | null = null;\n\n /** How far the keyboard intrudes into this view, in points. */\n protected readonly overlap = computed(() => {\n if (!this.enabled()) return 0;\n const { height, screenY } = this.keyboard();\n if (height === 0) return 0;\n const frame = this.frame();\n const offset = this.keyboardVerticalOffset();\n if (!frame) return Math.max(0, height - offset);\n // In `height` mode the frame is the one this view already shrank, so measure the height it\n // had before, as RN adds its current adjustment back; otherwise the room it made reads as no\n // overlap and the height springs back under the keyboard.\n const extent =\n this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;\n const overlap = screenY === undefined ? height - offset : frame.y + extent - (screenY - offset);\n return Math.min(extent, Math.max(0, overlap));\n });\n\n /**\n * The adjustment, or nothing to adjust. Bound as the engine's style override rather than as\n * host style, because Angular ranks a host style binding under the template's: a caller's\n * `[style]=\"{flex: 1}\"` would otherwise keep its flex in height mode and the view would not\n * shrink at all. RN composes the adjustment after the caller's style, so it wins there too.\n */\n protected readonly avoidance = computed(() => {\n const overlap = this.overlap();\n const behavior = this.behavior();\n if (overlap === 0 || behavior === 'position') return undefined;\n if (behavior === 'height') {\n const base = this.initialHeight ?? this.frame()?.height;\n return base === undefined ? undefined : { height: base - overlap, flex: 0 };\n }\n return { paddingBottom: overlap };\n });\n\n protected readonly positioned = computed(() => ({\n ...this.contentContainerStyle(),\n bottom: this.overlap(),\n }));\n\n constructor() {\n super();\n // A duration of zero is Android's own keyboard event, which animates itself; there is\n // nothing to configure alongside it. `appear`/`leave` are turned off because this is an\n // update to a view already on screen, not one appearing or leaving - RN's own bare\n // `LayoutAnimation.configureNext({duration, update})` animates nothing else either.\n effect(() => {\n const { duration, easing } = this.keyboard();\n if (!this.enabled() || !duration) return;\n void this.layoutAnimation.animate(() => {}, {\n duration,\n easing: layoutEasingOf(easing),\n appear: 'none',\n leave: 'none',\n });\n });\n }\n\n protected onLayout(event: NativeSyntheticEvent<{ layout?: Rect }>): void {\n const layout = event.nativeEvent?.layout;\n if (!layout) return;\n this.frame.set({ ...layout, y: this.windowTop(layout.y) });\n // Only a layout that the keyboard did not cause counts as the natural height.\n if (this.overlap() === 0) this.initialHeight = layout.height;\n }\n\n /**\n * Where this view's top edge is in the window, or `fallback` where the platform cannot say.\n * Synchronous on the new architecture, so it answers for the layout being reported.\n */\n private windowTop(fallback: number): number {\n let top = fallback;\n this.engine.measure(this.node, (frame) => (top = frame.y));\n return top;\n }\n}\n"]}
1
+ {"version":3,"file":"keyboard-avoiding-view.js","sourceRoot":"","sources":["../src/keyboard-avoiding-view.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n computed,\n effect,\n inject,\n input,\n signal,\n booleanAttribute,\n} from '@angular/core';\nimport { Keyboard, LayoutAnimation, type LayoutEasing } from '@ng-native/device';\nimport type { NativeSyntheticEvent } from '@ng-native/fabric';\nimport type { Rect } from './events.ts';\nimport { ContentContainer } from './content-container.ts';\nimport { View } from './view.ts';\nimport { ViewBase } from './view-base.ts';\nimport { numberOr } from './transforms.ts';\n\nexport type KeyboardAvoidingBehavior = 'padding' | 'height' | 'position';\n\n/**\n * The names `LayoutEasing` knows. `'keyboard'` is among them: iOS reports its keyboard's own\n * curve by that name, and RN's KeyboardAvoidingView animates on it.\n */\nconst LAYOUT_EASINGS: ReadonlySet<string> = new Set([\n 'linear',\n 'easeInEaseOut',\n 'easeIn',\n 'easeOut',\n 'spring',\n 'keyboard',\n]);\n\n/** A keyboard event's `easing`, narrowed to what `LayoutAnimation` accepts, or left to its default. */\nfunction layoutEasingOf(easing: string | undefined): LayoutEasing | undefined {\n return easing !== undefined && LAYOUT_EASINGS.has(easing) ? (easing as LayoutEasing) : undefined;\n}\n\n/**\n * Moves its content clear of the keyboard.\n *\n * RN's KeyboardAvoidingView is a JS composite: there is no native component, only a view whose\n * padding, height or position tracks the keyboard. Off a device the `Keyboard` service reports\n * nothing, so this renders as a plain view and does nothing, which is also the right behaviour on\n * a platform with no soft keyboard.\n *\n * The overlap is the view's own frame against the keyboard's top edge, so a view that does not\n * reach the bottom of the screen moves only as far as it must. Unlike RN, the frame's top is\n * measured in the window, which is what the keyboard's `screenY` is in: `(layout)` reports it in\n * the parent's coordinates, and on a screen under a navigation bar those start a bar's height\n * down. RN leaves that to `keyboardVerticalOffset`, and a view that was not told came up short by\n * exactly that much, its bottom edge still behind the keyboard. The overlap never exceeds the\n * view's own height either: padding a view by more than it has pushes its content out of it.\n * When the keyboard source reports no `screenY`, the keyboard's height stands in.\n *\n * The adjustment itself animates with the keyboard's own motion, the way RN's own\n * KeyboardAvoidingView does: a keyboard event's `duration` and `easing` configure the next\n * layout animation before the style change that moves this view, so the two run together rather\n * than the padding, height or position jumping to its resting place in one step. Android animates\n * no layout unless an app turns that on, and reports no duration, so there the view jumps; with\n * `@ng-native/expo`'s `provideNativeKeyboard()` it follows the keyboard frame by frame instead.\n */\n@Component({\n selector: 'keyboard-avoiding-view',\n imports: [ContentContainer, View],\n template: `\n @if (behavior() === 'position') {\n <view\n [contentContainerOf]=\"node\"\n [contentContainerClass]=\"contentContainerClass()\"\n [style]=\"positioned()\"\n ><ng-content\n /></view>\n } @else {\n <ng-content />\n }\n `,\n host: {\n '[styleOverride]': 'avoidance()',\n '(layout)': 'onLayout($event)',\n },\n})\nexport class KeyboardAvoidingView extends ViewBase {\n /**\n * How to make room: pad the bottom, shrink the height, or shift the content up. RN leaves this\n * unset and does nothing; `padding` is the default here because that is what a form wants.\n */\n readonly behavior = input<KeyboardAvoidingBehavior>('padding');\n /** Styles for the inner view that `position` moves. */\n readonly contentContainerStyle = input<Record<string, unknown>>();\n /**\n * Classes for the inner view that `position` moves, matched as if it were written in the\n * template this view is. See `ContentContainer`.\n */\n readonly contentContainerClass = input<string>();\n /** Set to false to stop avoiding without removing the view. */\n readonly enabled = input(true, { transform: booleanAttribute });\n /**\n * Extra room to leave above the keyboard, in points. RN's name, and in RN the place to put a\n * header's height; here the view is measured in the window, so a navigation bar needs none.\n */\n readonly keyboardVerticalOffset = input(0, { transform: numberOr(0) });\n\n private readonly keyboardService = inject(Keyboard);\n /** Where the keyboard is, at every frame of its motion where the platform says. */\n private readonly keyboard = this.keyboardService.position;\n private readonly layoutAnimation = inject(LayoutAnimation);\n private readonly frame = signal<Rect | null>(null);\n /** The height before any adjustment, which `height` mode shrinks from. */\n private initialHeight: number | null = null;\n\n /** How far the keyboard intrudes into this view, in points. */\n protected readonly overlap = computed(() => {\n if (!this.enabled()) return 0;\n const { height, screenY } = this.keyboard();\n if (height === 0) return 0;\n const frame = this.frame();\n const offset = this.keyboardVerticalOffset();\n if (!frame) return Math.max(0, height - offset);\n // In `height` mode the frame is the one this view already shrank, so measure the height it\n // had before, as RN adds its current adjustment back; otherwise the room it made reads as no\n // overlap and the height springs back under the keyboard.\n const extent =\n this.behavior() === 'height' ? (this.initialHeight ?? frame.height) : frame.height;\n const overlap = screenY === undefined ? height - offset : frame.y + extent - (screenY - offset);\n return Math.min(extent, Math.max(0, overlap));\n });\n\n /**\n * The adjustment, or nothing to adjust. Bound as the engine's style override rather than as\n * host style, because Angular ranks a host style binding under the template's: a caller's\n * `[style]=\"{flex: 1}\"` would otherwise keep its flex in height mode and the view would not\n * shrink at all. RN composes the adjustment after the caller's style, so it wins there too.\n */\n protected readonly avoidance = computed(() => {\n const overlap = this.overlap();\n const behavior = this.behavior();\n if (overlap === 0 || behavior === 'position') return undefined;\n if (behavior === 'height') {\n const base = this.initialHeight ?? this.frame()?.height;\n return base === undefined ? undefined : { height: base - overlap, flex: 0 };\n }\n return { paddingBottom: overlap };\n });\n\n protected readonly positioned = computed(() => ({\n ...this.contentContainerStyle(),\n bottom: this.overlap(),\n }));\n\n constructor() {\n super();\n // A duration of zero is Android's own keyboard event, which says nothing of the motion; there\n // is nothing to configure alongside it. Nor where the view follows the motion frame by frame.\n // `appear`/`leave` are turned off because this is an update to a view already on screen, not\n // one appearing or leaving - RN's own bare `LayoutAnimation.configureNext({duration, update})`\n // animates nothing else either.\n effect(() => {\n const { duration, easing } = this.keyboardService.metrics();\n if (!this.enabled() || !duration || this.keyboardService.followsMotion) return;\n void this.layoutAnimation.animate(() => {}, {\n duration,\n easing: layoutEasingOf(easing),\n appear: 'none',\n leave: 'none',\n });\n });\n }\n\n protected onLayout(event: NativeSyntheticEvent<{ layout?: Rect }>): void {\n const layout = event.nativeEvent?.layout;\n if (!layout) return;\n this.frame.set({ ...layout, y: this.windowTop(layout.y) });\n // Only a layout that the keyboard did not cause counts as the natural height.\n if (this.overlap() === 0) this.initialHeight = layout.height;\n }\n\n /**\n * Where this view's top edge is in the window, or `fallback` where the platform cannot say.\n * Synchronous on the new architecture, so it answers for the layout being reported.\n */\n private windowTop(fallback: number): number {\n let top = fallback;\n this.engine.measure(this.node, (frame) => (top = frame.y));\n return top;\n }\n}\n"]}
@@ -18,6 +18,15 @@ export declare class KeyboardControllerView {
18
18
  static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardControllerView, never>;
19
19
  static ɵdir: i0.ɵɵDirectiveDeclaration<KeyboardControllerView, "keyboard-controller-view", never, {}, {}, never, never, true, never>;
20
20
  }
21
+ /**
22
+ * The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at
23
+ * every frame of its motion. See `KeyboardDock`.
24
+ */
25
+ export declare class KeyboardMotionView {
26
+ constructor();
27
+ static ɵfac: i0.ɵɵFactoryDeclaration<KeyboardMotionView, never>;
28
+ static ɵdir: i0.ɵɵDirectiveDeclaration<KeyboardMotionView, "keyboard-motion-view", never, {}, {}, never, never, true, never>;
29
+ }
21
30
  /**
22
31
  * react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with
23
32
  * `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.
@@ -42,12 +51,17 @@ export declare class KeyboardGestureArea {
42
51
  * the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves
43
52
  * content above with it.
44
53
  *
45
- * Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it
46
- * learns as the keyboard starts to move; on Android it jumps into place then, rather than moving
47
- * with the keyboard. A window the system resized for the keyboard already puts it clear, and then
48
- * it is not padded at all. Android does not use the controller: a dock in a form sheet would be
49
- * lifted twice, by the sheet and by the keyboard, and the controller adds the system bars as
50
- * margins around the app's content unless it is told the app is edge-to-edge.
54
+ * On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by
55
+ * a view of that package's that reports how far the keyboard covers the dock at every frame. It is
56
+ * measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet
57
+ * react-native-screens raises with the keyboard, rises only as far as it still must. Android does
58
+ * not use react-native-keyboard-controller: its view adds the navigation bar as margins around the
59
+ * whole app's content, which an app drawn edge to edge does not want.
60
+ *
61
+ * Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the
62
+ * keyboard starts to move; on Android it jumps into place then, rather than moving with the
63
+ * keyboard. A window the system resized for the keyboard already puts it clear, and then it is not
64
+ * padded at all.
51
65
  *
52
66
  * `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
53
67
  * place, for content above that keeps clear of it with padding or an inset rather than moving.
@@ -63,7 +77,19 @@ export declare class KeyboardDock extends ViewBase {
63
77
  width: number;
64
78
  height: number;
65
79
  };
66
- /** iOS with the controller: native moves the bar. Read once: neither changes under an app. */
80
+ protected readonly overDock: {
81
+ position: string;
82
+ top: number;
83
+ bottom: number;
84
+ left: number;
85
+ width: number;
86
+ };
87
+ /** On Android, the view that reports the keyboard's motion, where the build has one. */
88
+ protected readonly motion: import("@ng-native/device").KeyboardMotion | null;
89
+ /**
90
+ * Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:
91
+ * neither changes under an app.
92
+ */
67
93
  protected readonly lifts: boolean;
68
94
  private readonly field;
69
95
  private readonly fieldInput;
@@ -71,7 +97,10 @@ export declare class KeyboardDock extends ViewBase {
71
97
  private readonly bottomEdge;
72
98
  /** The bar's own height, measured, which the drag that lowers the keyboard starts above. */
73
99
  protected readonly barHeight: import("@angular/core").WritableSignal<number>;
74
- /** The keyboard's height as the controller last reported it, excluding the drag area. */
100
+ /**
101
+ * The keyboard's height over the dock as native last reported it: the controller's keyboard
102
+ * height, excluding the drag area, or the motion view's cover of the dock.
103
+ */
75
104
  private readonly controlled;
76
105
  /** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */
77
106
  private readonly inset;
@@ -93,7 +122,7 @@ export declare class KeyboardDock extends ViewBase {
93
122
  */
94
123
  lift(view: HostNode, shift?: number | null): ScrollDrive | null;
95
124
  private liftBar;
96
- /** The controller's view, the one whose events carry the keyboard. */
125
+ /** The controller's or the motion view, the one whose events carry the keyboard. */
97
126
  private controllerView;
98
127
  /** The view holding the content, the last of the dock's own. */
99
128
  private barView;