aix 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/ios/HybridAix.swift +74 -6
- package/ios/HybridAixComposer.swift +63 -24
- package/lib/commonjs/aix.js +6 -2
- package/lib/commonjs/aix.js.map +1 -1
- package/lib/module/aix.js +6 -2
- package/lib/module/aix.js.map +1 -1
- package/lib/typescript/src/aix.d.ts +235 -11
- package/lib/typescript/src/aix.d.ts.map +1 -1
- package/lib/typescript/src/views/aix.nitro.d.ts +14 -0
- package/lib/typescript/src/views/aix.nitro.d.ts.map +1 -1
- package/nitrogen/generated/android/AixOnLoad.cpp +2 -0
- package/nitrogen/generated/android/c++/JAixStickToKeyboard.hpp +63 -0
- package/nitrogen/generated/android/c++/JAixStickToKeyboardOffset.hpp +61 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridAixComposerSpec.cpp +18 -3
- package/nitrogen/generated/android/c++/JHybridAixComposerSpec.hpp +2 -1
- package/nitrogen/generated/android/c++/JHybridAixSpec.cpp +18 -0
- package/nitrogen/generated/android/c++/JHybridAixSpec.hpp +2 -0
- package/nitrogen/generated/android/c++/views/JHybridAixComposerStateUpdater.cpp +4 -1
- package/nitrogen/generated/android/c++/views/JHybridAixStateUpdater.cpp +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/AixStickToKeyboard.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/AixStickToKeyboardOffset.kt +41 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/HybridAixComposerSpec.kt +5 -1
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/aix/HybridAixSpec.kt +14 -0
- package/nitrogen/generated/ios/Aix-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/Aix-Swift-Cxx-Bridge.hpp +73 -0
- package/nitrogen/generated/ios/Aix-Swift-Cxx-Umbrella.hpp +6 -0
- package/nitrogen/generated/ios/c++/HybridAixComposerSpecSwift.hpp +14 -3
- package/nitrogen/generated/ios/c++/HybridAixSpecSwift.hpp +7 -0
- package/nitrogen/generated/ios/c++/views/HybridAixComponent.mm +5 -0
- package/nitrogen/generated/ios/c++/views/HybridAixComposerComponent.mm +5 -1
- package/nitrogen/generated/ios/swift/AixStickToKeyboard.swift +59 -0
- package/nitrogen/generated/ios/swift/AixStickToKeyboardOffset.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridAixComposerSpec.swift +1 -1
- package/nitrogen/generated/ios/swift/HybridAixComposerSpec_cxx.swift +16 -1
- package/nitrogen/generated/ios/swift/HybridAixSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridAixSpec_cxx.swift +32 -0
- package/nitrogen/generated/shared/c++/AixStickToKeyboard.hpp +81 -0
- package/nitrogen/generated/shared/c++/AixStickToKeyboardOffset.hpp +79 -0
- package/nitrogen/generated/shared/c++/HybridAixComposerSpec.cpp +2 -1
- package/nitrogen/generated/shared/c++/HybridAixComposerSpec.hpp +6 -3
- package/nitrogen/generated/shared/c++/HybridAixSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridAixSpec.hpp +2 -0
- package/nitrogen/generated/shared/c++/views/HybridAixComponent.cpp +12 -0
- package/nitrogen/generated/shared/c++/views/HybridAixComponent.hpp +1 -0
- package/nitrogen/generated/shared/c++/views/HybridAixComposerComponent.cpp +12 -0
- package/nitrogen/generated/shared/c++/views/HybridAixComposerComponent.hpp +3 -1
- package/nitrogen/generated/shared/json/AixComposerConfig.json +1 -0
- package/nitrogen/generated/shared/json/AixConfig.json +1 -0
- package/package.json +6 -6
- package/src/aix.tsx +8 -1
- package/src/views/aix.nitro.ts +18 -1
package/README.md
CHANGED
|
@@ -97,7 +97,8 @@ The main container component that provides keyboard-aware behavior and manages s
|
|
|
97
97
|
|------|------|---------|-------------|
|
|
98
98
|
| `shouldStartAtEnd` | `boolean` | - | Whether the scroll view should start scrolled to the end of the content. |
|
|
99
99
|
| `scrollOnFooterSizeUpdate` | `object` | `{ enabled: true, scrolledToEndThreshold: 100, animated: false }` | Control the behavior of scrolling when the footer size changes. By default, changing the height of the footer will shift content up in the scroll view. |
|
|
100
|
-
| `scrollEndReachedThreshold` | `number` | `max(blankSize, 200)` | The number of pixels from the bottom of the scroll view to the end of the content that is considered "near the end". Used to determine if content should shift up when keyboard opens. |
|
|
100
|
+
| `scrollEndReachedThreshold` | `number` | `max(blankSize, 200)` | The number of pixels from the bottom of the scroll view to the end of the content that is considered "near the end". Used by `onScrolledNearEndChange` and to determine if content should shift up when keyboard opens. |
|
|
101
|
+
| `onScrolledNearEndChange` | `(isNearEnd: boolean) => void` | - | Callback fired when the scroll position transitions between "near end" and "not near end" states. Reactive to user scrolling, content size changes, parent size changes, and keyboard height changes. Uses `scrollEndReachedThreshold` to determine the threshold. |
|
|
101
102
|
| `additionalContentInsets` | `object` | - | Additional content insets applied when keyboard is open or closed. Shape: `{ top?: { whenKeyboardOpen, whenKeyboardClosed }, bottom?: { whenKeyboardOpen, whenKeyboardClosed } }` |
|
|
102
103
|
| `additionalScrollIndicatorInsets` | `object` | - | Additional insets for the scroll indicator, added to existing safe area insets. Applied to `verticalScrollIndicatorInsets` on iOS. |
|
|
103
104
|
| `mainScrollViewID` | `string` | - | The `nativeID` of the scroll view to use. If provided, will search for a scroll view with this `accessibilityIdentifier`. |
|
|
@@ -180,6 +181,36 @@ function Chat({ messages }) {
|
|
|
180
181
|
}
|
|
181
182
|
```
|
|
182
183
|
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
### Scroll to End Button
|
|
187
|
+
|
|
188
|
+
You can use `onScrolledNearEndChange` to show a "scroll to end" button when the user scrolls away from the bottom:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { Aix, useAixRef } from 'aix';
|
|
192
|
+
import { useState } from 'react';
|
|
193
|
+
|
|
194
|
+
function Chat() {
|
|
195
|
+
const aix = useAixRef();
|
|
196
|
+
const [isNearEnd, setIsNearEnd] = useState(false);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<Aix
|
|
200
|
+
ref={aix}
|
|
201
|
+
scrollEndReachedThreshold={200}
|
|
202
|
+
onScrolledNearEndChange={setIsNearEnd}
|
|
203
|
+
>
|
|
204
|
+
{/* ScrollView and messages... */}
|
|
205
|
+
|
|
206
|
+
{!isNearEnd && (
|
|
207
|
+
<Button onPress={() => aix.current?.scrollToEnd(true)} />
|
|
208
|
+
)}
|
|
209
|
+
</Aix>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
183
214
|
## TODOs
|
|
184
215
|
|
|
185
216
|
|
package/ios/HybridAix.swift
CHANGED
|
@@ -15,19 +15,19 @@ private var aixContextKey: UInt8 = 0
|
|
|
15
15
|
protocol AixContext: AnyObject {
|
|
16
16
|
/// The blank view (last cell) - used for calculating blank size
|
|
17
17
|
var blankView: HybridAixCellView? { get set }
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
/// The composer view
|
|
20
20
|
var composerView: HybridAixComposer? { get set }
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
/// Called when the blank view's size changes
|
|
23
23
|
func reportBlankViewSizeChange(size: CGSize, index: Int)
|
|
24
|
-
|
|
24
|
+
|
|
25
25
|
/// Register a cell with the context
|
|
26
26
|
func registerCell(_ cell: HybridAixCellView)
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
/// Unregister a cell from the context
|
|
29
29
|
func unregisterCell(_ cell: HybridAixCellView)
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
/// Register the composer view
|
|
32
32
|
func registerComposerView(_ composerView: HybridAixComposer)
|
|
33
33
|
|
|
@@ -36,6 +36,14 @@ protocol AixContext: AnyObject {
|
|
|
36
36
|
|
|
37
37
|
/// Called when the composer's height changes
|
|
38
38
|
func reportComposerHeightChange(height: CGFloat)
|
|
39
|
+
|
|
40
|
+
// MARK: - Keyboard State (for composer sticky behavior)
|
|
41
|
+
|
|
42
|
+
/// Current keyboard height
|
|
43
|
+
var keyboardHeight: CGFloat { get }
|
|
44
|
+
|
|
45
|
+
/// Keyboard height when fully open (for calculating progress)
|
|
46
|
+
var keyboardHeightWhenOpen: CGFloat { get }
|
|
39
47
|
}
|
|
40
48
|
|
|
41
49
|
extension UIView {
|
|
@@ -93,6 +101,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
93
101
|
|
|
94
102
|
var shouldApplyContentInsets: Bool? = nil
|
|
95
103
|
var onWillApplyContentInsets: ((_ insets: AixContentInsets) -> Void)? = nil
|
|
104
|
+
var onScrolledNearEndChange: ((_ isNearEnd: Bool) -> Void)? = nil
|
|
96
105
|
|
|
97
106
|
var additionalContentInsets: AixAdditionalContentInsetsProp?
|
|
98
107
|
|
|
@@ -199,6 +208,14 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
199
208
|
/// Flag to track if we're currently in an interactive keyboard dismiss
|
|
200
209
|
private var isInInteractiveDismiss = false
|
|
201
210
|
|
|
211
|
+
/// Previous "scrolled near end" state for change detection
|
|
212
|
+
private var prevIsScrolledNearEnd: Bool? = nil
|
|
213
|
+
|
|
214
|
+
/// KVO observation tokens for scroll view
|
|
215
|
+
private var contentOffsetObservation: NSKeyValueObservation?
|
|
216
|
+
private var contentSizeObservation: NSKeyValueObservation?
|
|
217
|
+
private var boundsObservation: NSKeyValueObservation?
|
|
218
|
+
|
|
202
219
|
// MARK: - Context References (weak to avoid retain cycles)
|
|
203
220
|
|
|
204
221
|
weak var blankView: HybridAixCellView? = nil {
|
|
@@ -244,12 +261,41 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
244
261
|
scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
|
245
262
|
|
|
246
263
|
setupPanGestureObserver()
|
|
264
|
+
setupScrollViewObservers(scrollView)
|
|
247
265
|
applyScrollIndicatorInsets()
|
|
248
266
|
}
|
|
249
267
|
|
|
250
268
|
return sv
|
|
251
269
|
}
|
|
252
270
|
|
|
271
|
+
/// Set up KVO observers on scroll view for contentOffset, contentSize, and bounds changes
|
|
272
|
+
private func setupScrollViewObservers(_ scrollView: UIScrollView) {
|
|
273
|
+
// Observe contentOffset (user scrolling)
|
|
274
|
+
contentOffsetObservation = scrollView.observe(\.contentOffset, options: [.new]) { [weak self] _, _ in
|
|
275
|
+
self?.updateScrolledNearEndState()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Observe contentSize (content size changes)
|
|
279
|
+
contentSizeObservation = scrollView.observe(\.contentSize, options: [.new]) { [weak self] _, _ in
|
|
280
|
+
self?.updateScrolledNearEndState()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Observe bounds (parent size changes)
|
|
284
|
+
boundsObservation = scrollView.observe(\.bounds, options: [.new]) { [weak self] _, _ in
|
|
285
|
+
self?.updateScrolledNearEndState()
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// Clean up KVO observers
|
|
290
|
+
private func removeScrollViewObservers() {
|
|
291
|
+
contentOffsetObservation?.invalidate()
|
|
292
|
+
contentOffsetObservation = nil
|
|
293
|
+
contentSizeObservation?.invalidate()
|
|
294
|
+
contentSizeObservation = nil
|
|
295
|
+
boundsObservation?.invalidate()
|
|
296
|
+
boundsObservation = nil
|
|
297
|
+
}
|
|
298
|
+
|
|
253
299
|
/// Set up observer on scroll view's pan gesture to detect interactive keyboard dismiss
|
|
254
300
|
private func setupPanGestureObserver() {
|
|
255
301
|
guard let scrollView = cachedScrollView else { return }
|
|
@@ -416,6 +462,19 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
416
462
|
if scrollView.contentInset.bottom != targetBottom {
|
|
417
463
|
scrollView.contentInset.bottom = targetBottom
|
|
418
464
|
}
|
|
465
|
+
|
|
466
|
+
// Update scrolled near end state after insets change
|
|
467
|
+
updateScrolledNearEndState()
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/// Centralized function to check and fire onScrolledNearEndChange callback
|
|
471
|
+
/// Called from KVO observers and after content inset changes
|
|
472
|
+
private func updateScrolledNearEndState() {
|
|
473
|
+
guard scrollView != nil else { return }
|
|
474
|
+
let isNearEnd = getIsScrolledNearEnd(distFromEnd: distFromEnd)
|
|
475
|
+
guard isNearEnd != prevIsScrolledNearEnd else { return }
|
|
476
|
+
prevIsScrolledNearEnd = isNearEnd
|
|
477
|
+
onScrolledNearEndChange?(isNearEnd)
|
|
419
478
|
}
|
|
420
479
|
|
|
421
480
|
/// Apply scroll indicator insets to the scroll view
|
|
@@ -568,9 +627,12 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
568
627
|
startEvent = event
|
|
569
628
|
}
|
|
570
629
|
}
|
|
571
|
-
|
|
630
|
+
|
|
572
631
|
// Update keyboard state
|
|
573
632
|
handleKeyboardMove(height: height, progress: progress)
|
|
633
|
+
|
|
634
|
+
// Update composer transform
|
|
635
|
+
composerView?.applyKeyboardTransform(height: height, heightWhenOpen: keyboardHeightWhenOpen, animated: false)
|
|
574
636
|
}
|
|
575
637
|
|
|
576
638
|
// MARK: - Initialization
|
|
@@ -587,6 +649,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
587
649
|
|
|
588
650
|
deinit {
|
|
589
651
|
removePanGestureObserver()
|
|
652
|
+
removeScrollViewObservers()
|
|
590
653
|
}
|
|
591
654
|
|
|
592
655
|
// MARK: - Lifecycle
|
|
@@ -657,6 +720,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
657
720
|
applyContentInset()
|
|
658
721
|
applyScrollIndicatorInsets()
|
|
659
722
|
scrollToEndInternal(animated: false)
|
|
723
|
+
updateScrolledNearEndState()
|
|
660
724
|
}
|
|
661
725
|
didScrollToEndInitially = true
|
|
662
726
|
} else {
|
|
@@ -812,6 +876,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
812
876
|
keyboardProgress = 1.0
|
|
813
877
|
applyContentInset()
|
|
814
878
|
applyScrollIndicatorInsets()
|
|
879
|
+
composerView?.applyKeyboardTransform(height: targetHeight, heightWhenOpen: keyboardHeightWhenOpen, animated: false)
|
|
815
880
|
return
|
|
816
881
|
}
|
|
817
882
|
|
|
@@ -826,6 +891,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
826
891
|
}
|
|
827
892
|
self.applyContentInset()
|
|
828
893
|
self.applyScrollIndicatorInsets()
|
|
894
|
+
self.composerView?.applyKeyboardTransform(height: targetHeight, heightWhenOpen: self.keyboardHeightWhenOpen, animated: false)
|
|
829
895
|
|
|
830
896
|
if let (startY, endY) = self.startEvent?.interpolateContentOffsetY {
|
|
831
897
|
self.scrollView?.setContentOffset(CGPoint(x: 0, y: endY), animated: false)
|
|
@@ -853,6 +919,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
853
919
|
self.keyboardProgress = 0
|
|
854
920
|
self.applyContentInset()
|
|
855
921
|
self.applyScrollIndicatorInsets()
|
|
922
|
+
self.composerView?.applyKeyboardTransform(height: 0, heightWhenOpen: self.keyboardHeightWhenOpen, animated: false)
|
|
856
923
|
}, completion: { [weak self] _ in
|
|
857
924
|
self?.handleKeyboardDidMove(height: 0, progress: 0)
|
|
858
925
|
})
|
|
@@ -865,6 +932,7 @@ class HybridAix: HybridAixSpec, AixContext, KeyboardNotificationsDelegate {
|
|
|
865
932
|
func keyboardDidHide(notification: NSNotification) {
|
|
866
933
|
print("[Aix] keyboardDidHide")
|
|
867
934
|
keyboardHeightWhenOpen = 0
|
|
935
|
+
composerView?.applyKeyboardTransform(height: 0, heightWhenOpen: 0, animated: false)
|
|
868
936
|
}
|
|
869
937
|
|
|
870
938
|
func keyboardWillChangeFrame(notification: NSNotification) {
|
|
@@ -12,23 +12,34 @@ import UIKit
|
|
|
12
12
|
/// It registers itself with the AixContext so the context can track composer height
|
|
13
13
|
/// for calculating content insets
|
|
14
14
|
class HybridAixComposer: HybridAixComposerSpec {
|
|
15
|
-
|
|
15
|
+
|
|
16
|
+
// MARK: - Props
|
|
17
|
+
|
|
18
|
+
var stickToKeyboard: AixStickToKeyboard? {
|
|
19
|
+
didSet {
|
|
20
|
+
// When stickToKeyboard changes, re-apply transform with current keyboard state
|
|
21
|
+
if let ctx = cachedAixContext {
|
|
22
|
+
applyKeyboardTransform(height: ctx.keyboardHeight, heightWhenOpen: ctx.keyboardHeightWhenOpen, animated: false)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
// MARK: - Inner View
|
|
17
|
-
|
|
28
|
+
|
|
18
29
|
/// Custom UIView that notifies owner when layout changes
|
|
19
30
|
private final class InnerView: UIView {
|
|
20
31
|
weak var owner: HybridAixComposer?
|
|
21
|
-
|
|
32
|
+
|
|
22
33
|
override func layoutSubviews() {
|
|
23
34
|
super.layoutSubviews()
|
|
24
35
|
owner?.handleLayoutChange()
|
|
25
36
|
}
|
|
26
|
-
|
|
37
|
+
|
|
27
38
|
override func didMoveToWindow() {
|
|
28
39
|
super.didMoveToWindow()
|
|
29
40
|
owner?.handleDidMoveToWindow()
|
|
30
41
|
}
|
|
31
|
-
|
|
42
|
+
|
|
32
43
|
override func willMove(toSuperview newSuperview: UIView?) {
|
|
33
44
|
super.willMove(toSuperview: newSuperview)
|
|
34
45
|
if newSuperview == nil {
|
|
@@ -36,37 +47,37 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
36
47
|
owner?.handleWillRemoveFromSuperview()
|
|
37
48
|
}
|
|
38
49
|
}
|
|
39
|
-
|
|
50
|
+
|
|
40
51
|
// MARK: - Touch Handling
|
|
41
|
-
|
|
52
|
+
|
|
42
53
|
/// Let touches pass through to React Native children
|
|
43
54
|
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
44
55
|
return false
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
|
-
|
|
58
|
+
|
|
48
59
|
// MARK: - Properties
|
|
49
|
-
|
|
60
|
+
|
|
50
61
|
/// The UIView for this composer
|
|
51
62
|
let view: UIView
|
|
52
|
-
|
|
63
|
+
|
|
53
64
|
/// Cached reference to the AixContext (found on first access)
|
|
54
65
|
private weak var cachedAixContext: AixContext?
|
|
55
66
|
|
|
56
67
|
/// Last reported height (to avoid reporting unchanged heights)
|
|
57
68
|
private var lastReportedHeight: CGFloat = 0
|
|
58
|
-
|
|
69
|
+
|
|
59
70
|
// MARK: - Initialization
|
|
60
|
-
|
|
71
|
+
|
|
61
72
|
override init() {
|
|
62
73
|
let inner = InnerView()
|
|
63
74
|
self.view = inner
|
|
64
75
|
super.init()
|
|
65
76
|
inner.owner = self
|
|
66
77
|
}
|
|
67
|
-
|
|
78
|
+
|
|
68
79
|
// MARK: - Context Access
|
|
69
|
-
|
|
80
|
+
|
|
70
81
|
/// Get the AixContext, caching it for performance
|
|
71
82
|
private func getAixContext() -> AixContext? {
|
|
72
83
|
if let cached = cachedAixContext {
|
|
@@ -76,22 +87,26 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
76
87
|
cachedAixContext = ctx
|
|
77
88
|
return ctx
|
|
78
89
|
}
|
|
79
|
-
|
|
90
|
+
|
|
80
91
|
// MARK: - Lifecycle Handlers
|
|
81
|
-
|
|
92
|
+
|
|
82
93
|
/// Called when the view is added to a window (full hierarchy is connected)
|
|
83
94
|
private func handleDidMoveToWindow() {
|
|
84
|
-
guard view.window != nil else {
|
|
85
|
-
|
|
95
|
+
guard view.window != nil else {
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
86
99
|
// Clear cached context since hierarchy changed
|
|
87
100
|
cachedAixContext = nil
|
|
88
|
-
|
|
101
|
+
|
|
89
102
|
// Register with the new context
|
|
90
103
|
if let ctx = getAixContext() {
|
|
91
104
|
ctx.registerComposerView(self)
|
|
105
|
+
// Initial state
|
|
106
|
+
applyKeyboardTransform(height: ctx.keyboardHeight, heightWhenOpen: ctx.keyboardHeightWhenOpen, animated: false)
|
|
92
107
|
}
|
|
93
108
|
}
|
|
94
|
-
|
|
109
|
+
|
|
95
110
|
/// Called when the view is about to be removed from superview
|
|
96
111
|
private func handleWillRemoveFromSuperview() {
|
|
97
112
|
// Unregister from context before removal
|
|
@@ -100,7 +115,7 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
100
115
|
}
|
|
101
116
|
cachedAixContext = nil
|
|
102
117
|
}
|
|
103
|
-
|
|
118
|
+
|
|
104
119
|
/// Called when layoutSubviews fires (size may have changed)
|
|
105
120
|
private func handleLayoutChange() {
|
|
106
121
|
let currentHeight = view.bounds.height
|
|
@@ -109,11 +124,35 @@ class HybridAixComposer: HybridAixComposerSpec {
|
|
|
109
124
|
getAixContext()?.reportComposerHeightChange(height: currentHeight)
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
|
-
|
|
127
|
+
|
|
128
|
+
// MARK: - Keyboard handling
|
|
129
|
+
|
|
130
|
+
/// Apply keyboard transform to move the composer with the keyboard
|
|
131
|
+
/// Called by the AixContext when keyboard state changes
|
|
132
|
+
func applyKeyboardTransform(height: CGFloat, heightWhenOpen: CGFloat, animated: Bool) {
|
|
133
|
+
guard let targetView = view.superview else {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
guard let settings = stickToKeyboard, settings.enabled else {
|
|
138
|
+
targetView.transform = .identity
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let progress = heightWhenOpen > 0 ? height / heightWhenOpen : 0
|
|
143
|
+
|
|
144
|
+
let offsetWhenClosed = CGFloat(settings.offset?.whenKeyboardClosed ?? 0)
|
|
145
|
+
let offsetWhenOpen = CGFloat(settings.offset?.whenKeyboardOpen ?? 0)
|
|
146
|
+
|
|
147
|
+
let currentOffset = offsetWhenClosed + (offsetWhenOpen - offsetWhenClosed) * progress
|
|
148
|
+
|
|
149
|
+
let translateY = -height - currentOffset
|
|
150
|
+
|
|
151
|
+
targetView.transform = CGAffineTransform(translationX: 0, y: translateY)
|
|
152
|
+
}
|
|
153
|
+
|
|
113
154
|
// MARK: - Deinitialization
|
|
114
|
-
|
|
115
155
|
deinit {
|
|
116
|
-
// Ensure we unregister when deallocated
|
|
117
156
|
cachedAixContext?.unregisterComposerView(self)
|
|
118
157
|
}
|
|
119
158
|
}
|
package/lib/commonjs/aix.js
CHANGED
|
@@ -7,9 +7,10 @@ exports.Aix = void 0;
|
|
|
7
7
|
var _reactNativeNitroModules = require("react-native-nitro-modules");
|
|
8
8
|
var _AixConfig = _interopRequireDefault(require("../nitrogen/generated/shared/json/AixConfig.json"));
|
|
9
9
|
var _react = require("react");
|
|
10
|
+
var _reactNativeReanimated = _interopRequireDefault(require("react-native-reanimated"));
|
|
10
11
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
-
const AixInternal = (0, _reactNativeNitroModules.getHostComponent)('Aix', () => _AixConfig.default);
|
|
13
|
+
const AixInternal = _reactNativeReanimated.default.createAnimatedComponent((0, _reactNativeNitroModules.getHostComponent)('Aix', () => _AixConfig.default));
|
|
13
14
|
|
|
14
15
|
// User-facing props type that accepts regular functions (not wrapped callbacks)
|
|
15
16
|
|
|
@@ -23,7 +24,10 @@ const Aix = exports.Aix = /*#__PURE__*/(0, _react.forwardRef)(function Aix(props
|
|
|
23
24
|
}
|
|
24
25
|
// Wrap onWillApplyContentInsets with callback() if provided
|
|
25
26
|
,
|
|
26
|
-
onWillApplyContentInsets: props.onWillApplyContentInsets ? (0, _reactNativeNitroModules.callback)(props.onWillApplyContentInsets) : undefined
|
|
27
|
+
onWillApplyContentInsets: props.onWillApplyContentInsets ? (0, _reactNativeNitroModules.callback)(props.onWillApplyContentInsets) : undefined
|
|
28
|
+
// Wrap onScrolledNearEndChange with callback() if provided
|
|
29
|
+
,
|
|
30
|
+
onScrolledNearEndChange: props.onScrolledNearEndChange ? (0, _reactNativeNitroModules.callback)(props.onScrolledNearEndChange) : undefined,
|
|
27
31
|
hybridRef: ref ? (0, _reactNativeNitroModules.callback)(r => {
|
|
28
32
|
if (typeof ref === 'function') {
|
|
29
33
|
ref(r);
|
package/lib/commonjs/aix.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNativeNitroModules","require","_AixConfig","_interopRequireDefault","_react","_jsxRuntime","e","__esModule","default","AixInternal","getHostComponent","AixConfig","Aix","exports","forwardRef","props","ref","jsx","scrollOnFooterSizeUpdate","enabled","scrolledToEndThreshold","animated","onWillApplyContentInsets","callback","undefined","hybridRef","r","current"],"sourceRoot":"../../src","sources":["aix.tsx"],"mappings":";;;;;;AAAA,IAAAA,wBAAA,GAAAC,OAAA;AAKA,IAAAC,UAAA,GAAAC,sBAAA,CAAAF,OAAA;AAEA,IAAAG,MAAA,GAAAH,OAAA;
|
|
1
|
+
{"version":3,"names":["_reactNativeNitroModules","require","_AixConfig","_interopRequireDefault","_react","_reactNativeReanimated","_jsxRuntime","e","__esModule","default","AixInternal","Animated","createAnimatedComponent","getHostComponent","AixConfig","Aix","exports","forwardRef","props","ref","jsx","scrollOnFooterSizeUpdate","enabled","scrolledToEndThreshold","animated","onWillApplyContentInsets","callback","undefined","onScrolledNearEndChange","hybridRef","r","current"],"sourceRoot":"../../src","sources":["aix.tsx"],"mappings":";;;;;;AAAA,IAAAA,wBAAA,GAAAC,OAAA;AAKA,IAAAC,UAAA,GAAAC,sBAAA,CAAAF,OAAA;AAEA,IAAAG,MAAA,GAAAH,OAAA;AACA,IAAAI,sBAAA,GAAAF,sBAAA,CAAAF,OAAA;AAA8C,IAAAK,WAAA,GAAAL,OAAA;AAAA,SAAAE,uBAAAI,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAI9C,MAAMG,WAAW,GAAGC,8BAAQ,CAACC,uBAAuB,CAAC,IAAAC,yCAAgB,EACnE,KAAK,EACL,MAAMC,kBACR,CAAC,CAAC;;AAEF;;AASO,MAAMC,GAAG,GAAAC,OAAA,CAAAD,GAAA,gBAAG,IAAAE,iBAAU,EAC3B,SAASF,GAAGA,CAACG,KAAK,EAAEC,GAAG,EAAE;EACvB,oBACE,IAAAb,WAAA,CAAAc,GAAA,EAACV,WAAW;IAAA,GACNQ,KAAK;IACTG,wBAAwB,EACtBH,KAAK,CAACG,wBAAwB,IAAI;MAChCC,OAAO,EAAE,IAAI;MACbC,sBAAsB,EAAE,GAAG;MAC3BC,QAAQ,EAAE;IACZ;IAEF;IAAA;IACAC,wBAAwB,EACtBP,KAAK,CAACO,wBAAwB,GAC1B,IAAAC,iCAAQ,EAACR,KAAK,CAACO,wBAAwB,CAAC,GACxCE;IAEN;IAAA;IACAC,uBAAuB,EACrBV,KAAK,CAACU,uBAAuB,GACzB,IAAAF,iCAAQ,EAACR,KAAK,CAACU,uBAAuB,CAAC,GACvCD,SACL;IACDE,SAAS,EACPV,GAAG,GACC,IAAAO,iCAAQ,EAAEI,CAAC,IAAK;MAChB,IAAI,OAAOX,GAAG,KAAK,UAAU,EAAE;QAC7BA,GAAG,CAACW,CAAC,CAAC;MACR,CAAC,MAAM;QACLX,GAAG,CAACY,OAAO,GAAGD,CAAC;MACjB;IACF,CAAC,CAAC,GACAH;EACL,CACF,CAAC;AAEN,CACF,CAAC","ignoreList":[]}
|
package/lib/module/aix.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { callback, getHostComponent } from 'react-native-nitro-modules';
|
|
4
4
|
import AixConfig from '../nitrogen/generated/shared/json/AixConfig.json';
|
|
5
5
|
import { forwardRef } from 'react';
|
|
6
|
+
import Animated from 'react-native-reanimated';
|
|
6
7
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
-
const AixInternal = getHostComponent('Aix', () => AixConfig);
|
|
8
|
+
const AixInternal = Animated.createAnimatedComponent(getHostComponent('Aix', () => AixConfig));
|
|
8
9
|
|
|
9
10
|
// User-facing props type that accepts regular functions (not wrapped callbacks)
|
|
10
11
|
|
|
@@ -18,7 +19,10 @@ export const Aix = /*#__PURE__*/forwardRef(function Aix(props, ref) {
|
|
|
18
19
|
}
|
|
19
20
|
// Wrap onWillApplyContentInsets with callback() if provided
|
|
20
21
|
,
|
|
21
|
-
onWillApplyContentInsets: props.onWillApplyContentInsets ? callback(props.onWillApplyContentInsets) : undefined
|
|
22
|
+
onWillApplyContentInsets: props.onWillApplyContentInsets ? callback(props.onWillApplyContentInsets) : undefined
|
|
23
|
+
// Wrap onScrolledNearEndChange with callback() if provided
|
|
24
|
+
,
|
|
25
|
+
onScrolledNearEndChange: props.onScrolledNearEndChange ? callback(props.onScrolledNearEndChange) : undefined,
|
|
22
26
|
hybridRef: ref ? callback(r => {
|
|
23
27
|
if (typeof ref === 'function') {
|
|
24
28
|
ref(r);
|
package/lib/module/aix.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["callback","getHostComponent","AixConfig","forwardRef","jsx","_jsx","AixInternal","Aix","props","ref","scrollOnFooterSizeUpdate","enabled","scrolledToEndThreshold","animated","onWillApplyContentInsets","undefined","hybridRef","r","current"],"sourceRoot":"../../src","sources":["aix.tsx"],"mappings":";;AAAA,SACEA,QAAQ,EACRC,gBAAgB,QAEX,4BAA4B;AACnC,OAAOC,SAAS,MAAM,kDAAkD;AAExE,SAASC,UAAU,QAA6B,OAAO;AAAA,SAAAC,GAAA,IAAAC,IAAA;
|
|
1
|
+
{"version":3,"names":["callback","getHostComponent","AixConfig","forwardRef","Animated","jsx","_jsx","AixInternal","createAnimatedComponent","Aix","props","ref","scrollOnFooterSizeUpdate","enabled","scrolledToEndThreshold","animated","onWillApplyContentInsets","undefined","onScrolledNearEndChange","hybridRef","r","current"],"sourceRoot":"../../src","sources":["aix.tsx"],"mappings":";;AAAA,SACEA,QAAQ,EACRC,gBAAgB,QAEX,4BAA4B;AACnC,OAAOC,SAAS,MAAM,kDAAkD;AAExE,SAASC,UAAU,QAA6B,OAAO;AACvD,OAAOC,QAAQ,MAAM,yBAAyB;AAAA,SAAAC,GAAA,IAAAC,IAAA;AAI9C,MAAMC,WAAW,GAAGH,QAAQ,CAACI,uBAAuB,CAACP,gBAAgB,CACnE,KAAK,EACL,MAAMC,SACR,CAAC,CAAC;;AAEF;;AASA,OAAO,MAAMO,GAAG,gBAAGN,UAAU,CAC3B,SAASM,GAAGA,CAACC,KAAK,EAAEC,GAAG,EAAE;EACvB,oBACEL,IAAA,CAACC,WAAW;IAAA,GACNG,KAAK;IACTE,wBAAwB,EACtBF,KAAK,CAACE,wBAAwB,IAAI;MAChCC,OAAO,EAAE,IAAI;MACbC,sBAAsB,EAAE,GAAG;MAC3BC,QAAQ,EAAE;IACZ;IAEF;IAAA;IACAC,wBAAwB,EACtBN,KAAK,CAACM,wBAAwB,GAC1BhB,QAAQ,CAACU,KAAK,CAACM,wBAAwB,CAAC,GACxCC;IAEN;IAAA;IACAC,uBAAuB,EACrBR,KAAK,CAACQ,uBAAuB,GACzBlB,QAAQ,CAACU,KAAK,CAACQ,uBAAuB,CAAC,GACvCD,SACL;IACDE,SAAS,EACPR,GAAG,GACCX,QAAQ,CAAEoB,CAAC,IAAK;MAChB,IAAI,OAAOT,GAAG,KAAK,UAAU,EAAE;QAC7BA,GAAG,CAACS,CAAC,CAAC;MACR,CAAC,MAAM;QACLT,GAAG,CAACU,OAAO,GAAGD,CAAC;MACjB;IACF,CAAC,CAAC,GACAH;EACL,CACF,CAAC;AAEN,CACF,CAAC","ignoreList":[]}
|