@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.
- package/dist/animated-style.js +1 -1
- package/dist/animated-style.js.map +1 -1
- package/dist/content-container.js +1 -1
- package/dist/content-container.js.map +1 -1
- package/dist/gestures.js +9 -1
- package/dist/gestures.js.map +1 -1
- package/dist/gradient-text.js +1 -1
- package/dist/gradient-text.js.map +1 -1
- package/dist/keyboard-avoiding-view.d.ts +5 -1
- package/dist/keyboard-avoiding-view.js +13 -8
- package/dist/keyboard-avoiding-view.js.map +1 -1
- package/dist/keyboard-dock.d.ts +38 -9
- package/dist/keyboard-dock.js +85 -35
- package/dist/keyboard-dock.js.map +1 -1
- package/dist/sticky-headers.js +1 -1
- package/dist/sticky-headers.js.map +1 -1
- package/dist/view-base.js +1 -1
- package/dist/view-base.js.map +1 -1
- package/dist/virtual-list.d.ts +13 -0
- package/dist/virtual-list.js +23 -3
- package/dist/virtual-list.js.map +1 -1
- package/package.json +4 -4
package/dist/keyboard-dock.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Component, DestroyRef, Directive, ElementRef, EnvironmentInjector, InjectionToken, afterRenderEffect, computed, contentChild, effect, inject, input, signal, untracked, } from '@angular/core';
|
|
2
|
-
import { Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';
|
|
2
|
+
import { KEYBOARD_MOTION, Keyboard, SCREEN_IN_FRONT, SafeArea } from '@ng-native/device';
|
|
3
3
|
import { claimHost, nativePlatform, registerViewName, } from '@ng-native/fabric';
|
|
4
4
|
import { TemplateSlot } from "./template-slot.js";
|
|
5
5
|
import { TextInput } from "./text-input.js";
|
|
@@ -30,6 +30,8 @@ const KEYBOARD_FEED = {
|
|
|
30
30
|
const FAR = 10_000;
|
|
31
31
|
/** A native view the dock renders for itself, out of the way of the layout. */
|
|
32
32
|
const OUT_OF_FLOW = { position: 'absolute', width: 0, height: 0 };
|
|
33
|
+
/** Over the dock, out of the way of the layout, so its bottom edge is the dock's. */
|
|
34
|
+
const OVER_DOCK = { position: 'absolute', top: 0, bottom: 0, left: 0, width: 0 };
|
|
33
35
|
/** react-native-keyboard-controller's view that reports the keyboard. See `KeyboardDock`. */
|
|
34
36
|
export class KeyboardControllerView {
|
|
35
37
|
constructor() {
|
|
@@ -46,6 +48,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
|
|
|
46
48
|
host: { '[enabled]': 'true' },
|
|
47
49
|
}]
|
|
48
50
|
}], ctorParameters: () => [] });
|
|
51
|
+
/**
|
|
52
|
+
* The native view `KEYBOARD_MOTION` names, which reports how far the keyboard covers the dock at
|
|
53
|
+
* every frame of its motion. See `KeyboardDock`.
|
|
54
|
+
*/
|
|
55
|
+
export class KeyboardMotionView {
|
|
56
|
+
constructor() {
|
|
57
|
+
const motion = inject(KEYBOARD_MOTION);
|
|
58
|
+
if (motion)
|
|
59
|
+
registerViewName('keyboard-motion-view', motion.viewName);
|
|
60
|
+
claimHost(inject(ElementRef).nativeElement);
|
|
61
|
+
}
|
|
62
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardMotionView, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
63
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.2.0", type: KeyboardMotionView, isStandalone: true, selector: "keyboard-motion-view", ngImport: i0 });
|
|
64
|
+
}
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardMotionView, decorators: [{
|
|
66
|
+
type: Directive,
|
|
67
|
+
args: [{ selector: 'keyboard-motion-view' }]
|
|
68
|
+
}], ctorParameters: () => [] });
|
|
49
69
|
/**
|
|
50
70
|
* react-native-keyboard-controller's drag area. On iOS it registers `offset` for the field with
|
|
51
71
|
* `textInputNativeID`, so an interactive dismissal takes the keyboard from that far above it.
|
|
@@ -82,12 +102,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
|
|
|
82
102
|
* the keyboard, as Messages does, and a short one springs it back up. `keyboardLift` moves
|
|
83
103
|
* content above with it.
|
|
84
104
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
105
|
+
* On Android with `@ng-native/expo`'s `provideNativeKeyboard()`, the bar is moved the same way, by
|
|
106
|
+
* a view of that package's that reports how far the keyboard covers the dock at every frame. It is
|
|
107
|
+
* measured on screen, so a dock that is not at the bottom of the window, or is in a form sheet
|
|
108
|
+
* react-native-screens raises with the keyboard, rises only as far as it still must. Android does
|
|
109
|
+
* not use react-native-keyboard-controller: its view adds the navigation bar as margins around the
|
|
110
|
+
* whole app's content, which an app drawn edge to edge does not want.
|
|
111
|
+
*
|
|
112
|
+
* Otherwise the bar is padded by as much of the keyboard as covers it, which it learns as the
|
|
113
|
+
* keyboard starts to move; on Android it jumps into place then, rather than moving with the
|
|
114
|
+
* keyboard. A window the system resized for the keyboard already puts it clear, and then it is not
|
|
115
|
+
* padded at all.
|
|
91
116
|
*
|
|
92
117
|
* `covered()` is how much of the bottom of the screen the keyboard covers beyond the dock's own
|
|
93
118
|
* place, for content above that keeps clear of it with padding or an inset rather than moving.
|
|
@@ -100,8 +125,14 @@ export class KeyboardDock extends ViewBase {
|
|
|
100
125
|
safeArea = inject(SafeArea);
|
|
101
126
|
none = {};
|
|
102
127
|
outOfFlow = OUT_OF_FLOW;
|
|
103
|
-
|
|
104
|
-
|
|
128
|
+
overDock = OVER_DOCK;
|
|
129
|
+
/** On Android, the view that reports the keyboard's motion, where the build has one. */
|
|
130
|
+
motion = nativePlatform() === 'android' ? inject(KEYBOARD_MOTION) : null;
|
|
131
|
+
/**
|
|
132
|
+
* Native moves the bar: on iOS with the controller, on Android with the motion view. Read once:
|
|
133
|
+
* neither changes under an app.
|
|
134
|
+
*/
|
|
135
|
+
lifts = this.motion !== null || (nativePlatform() === 'ios' && inject(KEYBOARD_CONTROLLER));
|
|
105
136
|
field = contentChild(TextInput, { ...(ngDevMode ? { debugName: "field" } : /* istanbul ignore next */ {}), descendants: true, read: ElementRef });
|
|
106
137
|
fieldInput = contentChild(TextInput, { ...(ngDevMode ? { debugName: "fieldInput" } : /* istanbul ignore next */ {}), descendants: true });
|
|
107
138
|
/** Where the dock's bottom edge is in the window, which the keyboard is compared to. */
|
|
@@ -110,7 +141,10 @@ export class KeyboardDock extends ViewBase {
|
|
|
110
141
|
/** The bar's own height, measured, which the drag that lowers the keyboard starts above. */
|
|
111
142
|
barHeight = signal(0, /* @ts-ignore */
|
|
112
143
|
...(ngDevMode ? [{ debugName: "barHeight" }] : /* istanbul ignore next */ []));
|
|
113
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* The keyboard's height over the dock as native last reported it: the controller's keyboard
|
|
146
|
+
* height, excluding the drag area, or the motion view's cover of the dock.
|
|
147
|
+
*/
|
|
114
148
|
controlled = signal(0, /* @ts-ignore */
|
|
115
149
|
...(ngDevMode ? [{ debugName: "controlled" }] : /* istanbul ignore next */ []));
|
|
116
150
|
/** The inset under the bar while the keyboard is down: the home indicator or navigation bar. */
|
|
@@ -195,14 +229,14 @@ export class KeyboardDock extends ViewBase {
|
|
|
195
229
|
});
|
|
196
230
|
inject(DestroyRef).onDestroy(() => drive?.stop());
|
|
197
231
|
}
|
|
198
|
-
/** The controller's view, the one whose events carry the keyboard. */
|
|
232
|
+
/** The controller's or the motion view, the one whose events carry the keyboard. */
|
|
199
233
|
controllerView() {
|
|
200
|
-
this.source ??= this.node.
|
|
234
|
+
this.source ??= this.node.childList.find((child) => child.nodeKind === 'element') ?? this.node;
|
|
201
235
|
return this.source;
|
|
202
236
|
}
|
|
203
237
|
/** The view holding the content, the last of the dock's own. */
|
|
204
238
|
barView() {
|
|
205
|
-
return this.node.
|
|
239
|
+
return this.node.childList.filter((child) => child.nodeKind === 'element').at(-1);
|
|
206
240
|
}
|
|
207
241
|
onKeyboardMove(event) {
|
|
208
242
|
const height = event.nativeEvent?.height;
|
|
@@ -223,16 +257,24 @@ export class KeyboardDock extends ViewBase {
|
|
|
223
257
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.0", type: KeyboardDock, isStandalone: true, selector: "keyboard-dock", inputs: { backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "layout": "onLayout()" }, properties: { "style.paddingBottom": "padding()", "style.backgroundColor": "backgroundColor()" } }, queries: [{ propertyName: "field", first: true, predicate: TextInput, descendants: true, read: ElementRef, isSignal: true }, { propertyName: "fieldInput", first: true, predicate: TextInput, descendants: true, isSignal: true }], exportAs: ["keyboardDock"], usesInheritance: true, ngImport: i0, template: `
|
|
224
258
|
<ng-template #content><ng-content /></ng-template>
|
|
225
259
|
@if (lifts) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
260
|
+
@if (motion) {
|
|
261
|
+
<keyboard-motion-view
|
|
262
|
+
[style]="overDock"
|
|
263
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
264
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
265
|
+
/>
|
|
266
|
+
} @else {
|
|
267
|
+
<keyboard-controller-view
|
|
268
|
+
[style]="outOfFlow"
|
|
269
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
270
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
271
|
+
/>
|
|
272
|
+
<keyboard-gesture-area
|
|
273
|
+
[style]="outOfFlow"
|
|
274
|
+
[offset]="barHeight()"
|
|
275
|
+
[textInputNativeID]="inputID()"
|
|
276
|
+
/>
|
|
277
|
+
}
|
|
236
278
|
<view
|
|
237
279
|
collapsable="false"
|
|
238
280
|
[style.backgroundColor]="backgroundColor()"
|
|
@@ -242,29 +284,37 @@ export class KeyboardDock extends ViewBase {
|
|
|
242
284
|
} @else {
|
|
243
285
|
<ng-container [templateSlot]="content" [templateSlotContext]="none" />
|
|
244
286
|
}
|
|
245
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: KeyboardControllerView, selector: "keyboard-controller-view" }, { kind: "directive", type: KeyboardGestureArea, selector: "keyboard-gesture-area", inputs: ["offset", "textInputNativeID"] }, { kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: View, selector: "view" }] });
|
|
287
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: KeyboardControllerView, selector: "keyboard-controller-view" }, { kind: "directive", type: KeyboardGestureArea, selector: "keyboard-gesture-area", inputs: ["offset", "textInputNativeID"] }, { kind: "directive", type: KeyboardMotionView, selector: "keyboard-motion-view" }, { kind: "directive", type: TemplateSlot, selector: "[templateSlot]", inputs: ["templateSlot", "templateSlotContext"] }, { kind: "directive", type: View, selector: "view" }] });
|
|
246
288
|
}
|
|
247
289
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: KeyboardDock, decorators: [{
|
|
248
290
|
type: Component,
|
|
249
291
|
args: [{
|
|
250
292
|
selector: 'keyboard-dock',
|
|
251
293
|
exportAs: 'keyboardDock',
|
|
252
|
-
imports: [KeyboardControllerView, KeyboardGestureArea, TemplateSlot, View],
|
|
294
|
+
imports: [KeyboardControllerView, KeyboardGestureArea, KeyboardMotionView, TemplateSlot, View],
|
|
253
295
|
// One slot, stamped in whichever branch applies: two `<ng-content>`s of the same selector would
|
|
254
296
|
// be given the content at compile time, whichever branch renders.
|
|
255
297
|
template: `
|
|
256
298
|
<ng-template #content><ng-content /></ng-template>
|
|
257
299
|
@if (lifts) {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
300
|
+
@if (motion) {
|
|
301
|
+
<keyboard-motion-view
|
|
302
|
+
[style]="overDock"
|
|
303
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
304
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
305
|
+
/>
|
|
306
|
+
} @else {
|
|
307
|
+
<keyboard-controller-view
|
|
308
|
+
[style]="outOfFlow"
|
|
309
|
+
(keyboardMoveStart)="onKeyboardMove($event)"
|
|
310
|
+
(keyboardMoveEnd)="onKeyboardMove($event)"
|
|
311
|
+
/>
|
|
312
|
+
<keyboard-gesture-area
|
|
313
|
+
[style]="outOfFlow"
|
|
314
|
+
[offset]="barHeight()"
|
|
315
|
+
[textInputNativeID]="inputID()"
|
|
316
|
+
/>
|
|
317
|
+
}
|
|
268
318
|
<view
|
|
269
319
|
collapsable="false"
|
|
270
320
|
[style.backgroundColor]="backgroundColor()"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard-dock.js","sourceRoot":"","sources":["../src/keyboard-dock.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n Component,\n DestroyRef,\n Directive,\n ElementRef,\n EnvironmentInjector,\n InjectionToken,\n afterRenderEffect,\n computed,\n contentChild,\n effect,\n inject,\n input,\n signal,\n untracked,\n type Provider,\n} from '@angular/core';\nimport { Keyboard, 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\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 * 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 * Without it, and on Android, the bar is padded by as much of the keyboard as covers it, which it\n * learns as the keyboard starts to move; on Android it jumps into place then, rather than moving\n * with the keyboard. A window the system resized for the keyboard already puts it clear, and then\n * it is not padded at all. Android does not use the controller: a dock in a form sheet would be\n * lifted twice, by the sheet and by the keyboard, and the controller adds the system bars as\n * margins around the app's content unless it is told the app is edge-to-edge.\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, 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 <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 <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\n /** iOS with the controller: native moves the bar. Read once: neither changes under an app. */\n protected readonly lifts = 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 /** The keyboard's height as the controller last reported it, excluding the drag area. */\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 view, the one whose events carry the keyboard. */\n private controllerView(): HostNode {\n this.source ??= this.node.children.find((child) => child.kind === '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.children.filter((child) => child.kind === '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 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"]}
|
package/dist/sticky-headers.js
CHANGED
|
@@ -41,7 +41,7 @@ export class StickyHeaders {
|
|
|
41
41
|
scroll;
|
|
42
42
|
/** Find the sticky children again, after a pass that may have changed them. */
|
|
43
43
|
track(content, indices) {
|
|
44
|
-
const children = (content?.
|
|
44
|
+
const children = (content?.childList ?? []).filter((child) => child.nodeKind === 'element');
|
|
45
45
|
const headers = indices.flatMap((index) => children[index] ?? []);
|
|
46
46
|
for (const node of this.headers) {
|
|
47
47
|
if (!headers.includes(node))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sticky-headers.js","sourceRoot":"","sources":["../src/sticky-headers.ts"],"names":[],"mappings":"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","sourcesContent":["import { type HostEngine, type HostNode, type ScrollDrive, pinnedRange } from '@ng-native/fabric';\n\ninterface Extent {\n readonly start: number;\n readonly size: number;\n}\n\n/**\n * How long scrolling has to pause before a natively moved header's translate is written, so the\n * shadow tree, which a touch is hit-tested against, catches up with where native drew it. RN's\n * `ScrollViewStickyHeader` debounces the same write, by 64 ms on iOS.\n */\nexport const STICKY_SETTLE_MS = 64;\n\ntype LayoutEvent = {\n nativeEvent?: { layout?: { x?: number; y?: number; width?: number; height?: number } };\n};\n\n/**\n * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.\n *\n * There is no native prop for it on either platform. RN wraps each sticky child in\n * `ScrollViewStickyHeader`, which translates it by the scroll offset past its own position and\n * stops where the next sticky child would push it off. The same sums run here, over the content\n * view's element children: control-flow anchors are not children in RN's sense and are skipped,\n * so an index counts what is committed.\n *\n * Each sticky child's position comes from its own layout event, and the translation is written\n * as its `transform` prop, with RN's `zIndex: 10` so it draws over the content scrolling under\n * it. A style of the caller's own that sets either wins, as it would in RN.\n *\n * Where the host can, native moves each one on every frame the scroll view moves, as RN's\n * animated interpolation does, and the translation is written only once scrolling pauses: a\n * translate written from each scroll event lands late, and on a fast fling the header shudders.\n */\nexport class StickyHeaders {\n private readonly engine: HostEngine;\n private readonly horizontal: () => boolean;\n private headers: HostNode[] = [];\n private readonly extents = new Map<HostNode, Extent>();\n private readonly listening = new Map<HostNode, () => void>();\n /** The translation last written per header, so an unchanged one is not written again. */\n private readonly written = new Map<HostNode, number>();\n private readonly drives = new Map<HostNode, ScrollDrive>();\n private settleTimer: ReturnType<typeof setTimeout> | undefined;\n private offset = 0;\n\n constructor(engine: HostEngine, scroll: HostNode, horizontal: () => boolean) {\n this.engine = engine;\n this.scroll = scroll;\n this.horizontal = horizontal;\n }\n\n private readonly scroll: HostNode;\n\n /** Find the sticky children again, after a pass that may have changed them. */\n track(content: HostNode | undefined, indices: readonly number[]): void {\n const children = (content?.children ?? []).filter((child) => child.kind === 'element');\n const headers = indices.flatMap((index) => children[index] ?? []);\n for (const node of this.headers) {\n if (!headers.includes(node)) this.release(node);\n }\n for (const node of headers) {\n if (this.listening.has(node)) continue;\n this.engine.setProp(node, 'zIndex', 10);\n this.listening.set(\n node,\n this.engine.setEventListener(node, 'topLayout', (event) => this.measured(node, event)),\n );\n }\n this.headers = headers;\n this.apply();\n }\n\n scrolled(offset: number): void {\n this.offset = offset;\n if (!this.engine.drivesScroll) return this.apply();\n clearTimeout(this.settleTimer);\n this.settleTimer = setTimeout(() => this.apply(), STICKY_SETTLE_MS);\n }\n\n destroy(): void {\n clearTimeout(this.settleTimer);\n for (const node of this.headers) this.release(node);\n this.headers = [];\n }\n\n private measured(node: HostNode, event: unknown): void {\n const layout = (event as LayoutEvent).nativeEvent?.layout;\n if (!layout) return;\n const horizontal = this.horizontal();\n this.extents.set(node, {\n start: (horizontal ? layout.x : layout.y) ?? 0,\n size: (horizontal ? layout.width : layout.height) ?? 0,\n });\n this.apply();\n }\n\n private release(node: HostNode): void {\n this.listening.get(node)?.();\n this.listening.delete(node);\n this.extents.delete(node);\n this.written.delete(node);\n this.drives.get(node)?.stop();\n this.drives.delete(node);\n this.engine.setProp(node, 'zIndex', undefined);\n this.engine.setProp(node, 'transform', undefined);\n }\n\n private drive(node: HostNode, start: number, stop: number): void {\n if (!this.engine.drivesScroll) return;\n const range = pinnedRange(start, stop);\n const drive = this.drives.get(node);\n if (drive) return drive.update(range);\n const axis = this.horizontal() ? 'x' : 'y';\n const next = this.engine.driveByScroll(node, this.scroll, axis, range);\n if (next) this.drives.set(node, next);\n }\n\n /** `ScrollViewStickyHeader`'s interpolation, as plain arithmetic. */\n private apply(): void {\n const key = this.horizontal() ? 'translateX' : 'translateY';\n this.headers.forEach((node, at) => {\n const own = this.extents.get(node);\n if (!own) return;\n const next = this.extents.get(this.headers[at + 1]!);\n const stop = next ? next.start - own.size : Infinity;\n this.drive(node, own.start, stop);\n const shift = Math.min(Math.max(0, this.offset - own.start), Math.max(0, stop - own.start));\n if (this.written.get(node) === shift) return;\n this.written.set(node, shift);\n this.engine.setProp(node, 'transform', shift > 0 ? [{ [key]: shift }] : undefined);\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"sticky-headers.js","sourceRoot":"","sources":["../src/sticky-headers.ts"],"names":[],"mappings":"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","sourcesContent":["import { type HostEngine, type HostNode, type ScrollDrive, pinnedRange } from '@ng-native/fabric';\n\ninterface Extent {\n readonly start: number;\n readonly size: number;\n}\n\n/**\n * How long scrolling has to pause before a natively moved header's translate is written, so the\n * shadow tree, which a touch is hit-tested against, catches up with where native drew it. RN's\n * `ScrollViewStickyHeader` debounces the same write, by 64 ms on iOS.\n */\nexport const STICKY_SETTLE_MS = 64;\n\ntype LayoutEvent = {\n nativeEvent?: { layout?: { x?: number; y?: number; width?: number; height?: number } };\n};\n\n/**\n * `stickyHeaderIndices` for `<scroll-view>`, done the way RN's `ScrollView.js` does it.\n *\n * There is no native prop for it on either platform. RN wraps each sticky child in\n * `ScrollViewStickyHeader`, which translates it by the scroll offset past its own position and\n * stops where the next sticky child would push it off. The same sums run here, over the content\n * view's element children: control-flow anchors are not children in RN's sense and are skipped,\n * so an index counts what is committed.\n *\n * Each sticky child's position comes from its own layout event, and the translation is written\n * as its `transform` prop, with RN's `zIndex: 10` so it draws over the content scrolling under\n * it. A style of the caller's own that sets either wins, as it would in RN.\n *\n * Where the host can, native moves each one on every frame the scroll view moves, as RN's\n * animated interpolation does, and the translation is written only once scrolling pauses: a\n * translate written from each scroll event lands late, and on a fast fling the header shudders.\n */\nexport class StickyHeaders {\n private readonly engine: HostEngine;\n private readonly horizontal: () => boolean;\n private headers: HostNode[] = [];\n private readonly extents = new Map<HostNode, Extent>();\n private readonly listening = new Map<HostNode, () => void>();\n /** The translation last written per header, so an unchanged one is not written again. */\n private readonly written = new Map<HostNode, number>();\n private readonly drives = new Map<HostNode, ScrollDrive>();\n private settleTimer: ReturnType<typeof setTimeout> | undefined;\n private offset = 0;\n\n constructor(engine: HostEngine, scroll: HostNode, horizontal: () => boolean) {\n this.engine = engine;\n this.scroll = scroll;\n this.horizontal = horizontal;\n }\n\n private readonly scroll: HostNode;\n\n /** Find the sticky children again, after a pass that may have changed them. */\n track(content: HostNode | undefined, indices: readonly number[]): void {\n const children = (content?.childList ?? []).filter((child) => child.nodeKind === 'element');\n const headers = indices.flatMap((index) => children[index] ?? []);\n for (const node of this.headers) {\n if (!headers.includes(node)) this.release(node);\n }\n for (const node of headers) {\n if (this.listening.has(node)) continue;\n this.engine.setProp(node, 'zIndex', 10);\n this.listening.set(\n node,\n this.engine.setEventListener(node, 'topLayout', (event) => this.measured(node, event)),\n );\n }\n this.headers = headers;\n this.apply();\n }\n\n scrolled(offset: number): void {\n this.offset = offset;\n if (!this.engine.drivesScroll) return this.apply();\n clearTimeout(this.settleTimer);\n this.settleTimer = setTimeout(() => this.apply(), STICKY_SETTLE_MS);\n }\n\n destroy(): void {\n clearTimeout(this.settleTimer);\n for (const node of this.headers) this.release(node);\n this.headers = [];\n }\n\n private measured(node: HostNode, event: unknown): void {\n const layout = (event as LayoutEvent).nativeEvent?.layout;\n if (!layout) return;\n const horizontal = this.horizontal();\n this.extents.set(node, {\n start: (horizontal ? layout.x : layout.y) ?? 0,\n size: (horizontal ? layout.width : layout.height) ?? 0,\n });\n this.apply();\n }\n\n private release(node: HostNode): void {\n this.listening.get(node)?.();\n this.listening.delete(node);\n this.extents.delete(node);\n this.written.delete(node);\n this.drives.get(node)?.stop();\n this.drives.delete(node);\n this.engine.setProp(node, 'zIndex', undefined);\n this.engine.setProp(node, 'transform', undefined);\n }\n\n private drive(node: HostNode, start: number, stop: number): void {\n if (!this.engine.drivesScroll) return;\n const range = pinnedRange(start, stop);\n const drive = this.drives.get(node);\n if (drive) return drive.update(range);\n const axis = this.horizontal() ? 'x' : 'y';\n const next = this.engine.driveByScroll(node, this.scroll, axis, range);\n if (next) this.drives.set(node, next);\n }\n\n /** `ScrollViewStickyHeader`'s interpolation, as plain arithmetic. */\n private apply(): void {\n const key = this.horizontal() ? 'translateX' : 'translateY';\n this.headers.forEach((node, at) => {\n const own = this.extents.get(node);\n if (!own) return;\n const next = this.extents.get(this.headers[at + 1]!);\n const stop = next ? next.start - own.size : Infinity;\n this.drive(node, own.start, stop);\n const shift = Math.min(Math.max(0, this.offset - own.start), Math.max(0, stop - own.start));\n if (this.written.get(node) === shift) return;\n this.written.set(node, shift);\n this.engine.setProp(node, 'transform', shift > 0 ? [{ [key]: shift }] : undefined);\n });\n }\n}\n"]}
|
package/dist/view-base.js
CHANGED
|
@@ -147,7 +147,7 @@ export class ViewBase {
|
|
|
147
147
|
* off the node where the host put it there, which is one property read against an injector
|
|
148
148
|
* lookup that walks every ancestor element, for every element on screen.
|
|
149
149
|
*/
|
|
150
|
-
engine = this.node.
|
|
150
|
+
engine = this.node.engine ?? inject(HostEngine);
|
|
151
151
|
/**
|
|
152
152
|
* See `contributeAccessibility`. Empty for every element nothing is composed onto. Read, never
|
|
153
153
|
* cached, from `ngOnInit` onwards: by then every directive on the node has been constructed and
|