react-native-dodge-keyboard 1.0.2 → 1.0.4
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/index.d.ts +15 -3
- package/index.js +418 -254
- package/package.json +1 -1
- package/TODO +0 -1
package/index.d.ts
CHANGED
|
@@ -63,12 +63,15 @@ export interface DodgeKeyboardProps {
|
|
|
63
63
|
/**
|
|
64
64
|
* an handler used internally for checking if a view is focused
|
|
65
65
|
*
|
|
66
|
+
* @param {View} ref - the reference of the view to check if it is currently focused
|
|
67
|
+
* @param {View} refList - list of all views references that were marked as `focusable`
|
|
68
|
+
*
|
|
66
69
|
* @default
|
|
67
70
|
* ```js
|
|
68
71
|
* r => r?.isFocused?.()
|
|
69
72
|
* ```
|
|
70
73
|
*/
|
|
71
|
-
checkIfElementIsFocused?: (ref: View) => boolean;
|
|
74
|
+
checkIfElementIsFocused?: (ref: View, refList: View[]) => boolean;
|
|
72
75
|
|
|
73
76
|
/**
|
|
74
77
|
* Child element(s) wrapped by the dodge container.
|
|
@@ -97,11 +100,20 @@ interface doHijackResult {
|
|
|
97
100
|
|
|
98
101
|
interface ReactHijackerProps {
|
|
99
102
|
doHijack: (node: React.ReactNode, path: Array<any> | undefined) => doHijackResult,
|
|
100
|
-
|
|
103
|
+
enableLocator?: boolean | undefined;
|
|
101
104
|
children?: React.ReactNode;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
export function ReactHijacker(props: ReactHijackerProps): React.ReactElement | null;
|
|
108
|
+
export function __HijackNode(props: { children: () => React.ReactElement | null }): React.ReactElement | null;
|
|
109
|
+
|
|
110
|
+
export function createHijackedElement(element?: React.ReactElement | null): { __element: React.ReactElement | null };
|
|
105
111
|
|
|
106
112
|
export function isDodgeScrollable(element: React.ReactNode, disableTagCheck?: boolean): boolean;
|
|
107
|
-
export function isDodgeInput(element: React.ReactNode, disableTagCheck?: boolean): boolean;
|
|
113
|
+
export function isDodgeInput(element: React.ReactNode, disableTagCheck?: boolean): boolean;
|
|
114
|
+
|
|
115
|
+
interface KeyboardPlaceholderProps {
|
|
116
|
+
doHeight: (keyboardheight: number) => number;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function KeyboardPlaceholderView(props: KeyboardPlaceholderProps): React.ReactElement | null;
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Children, cloneElement, createElement, forwardRef, isValidElement, memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
-
import { Dimensions, findNodeHandle, Keyboard, StyleSheet, UIManager } from "react-native";
|
|
2
|
+
import { Animated, Dimensions, findNodeHandle, Keyboard, Platform, StyleSheet, UIManager, useAnimatedValue } from "react-native";
|
|
3
3
|
|
|
4
4
|
export default function ({ children, offset = 10, disabled, onHandleDodging, disableTagCheck, checkIfElementIsFocused }) {
|
|
5
5
|
if (checkIfElementIsFocused !== undefined) {
|
|
@@ -7,7 +7,7 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
7
7
|
throw 'checkIfElementIsFocused should be a function';
|
|
8
8
|
|
|
9
9
|
checkIfElementIsFocused = niceFunction(checkIfElementIsFocused, 'checkIfElementIsFocused');
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
11
|
|
|
12
12
|
if (onHandleDodging !== undefined) {
|
|
13
13
|
if (typeof onHandleDodging !== 'function')
|
|
@@ -24,91 +24,156 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
24
24
|
* @type {import("react").RefObject<{[key: string]: { __is_standalone: boolean, _standalone_props: { dodge_keyboard_offset?: number, dodge_keyboard_lift?: boolean }, scrollRef: import("react-native").ScrollView, inputRef: {[key: string]: { ref: import("react-native").TextInput, props: { dodge_keyboard_offset?: number, dodge_keyboard_lift?: boolean } }} }}>}
|
|
25
25
|
*/
|
|
26
26
|
const viewRefsMap = useRef({});
|
|
27
|
-
const isKeyboardVisible = useRef();
|
|
28
27
|
const doDodgeKeyboard = useRef();
|
|
29
28
|
const previousLift = useRef();
|
|
29
|
+
const wasVisible = useRef();
|
|
30
|
+
const pendingIdleTask = useRef();
|
|
31
|
+
const lastKeyboardEvent = useRef();
|
|
30
32
|
|
|
31
33
|
const clearPreviousDodge = (scrollId) => {
|
|
32
34
|
if (previousLift.current && previousLift.current !== scrollId) {
|
|
33
35
|
const viewRef = viewRefsMap.current[previousLift.current]?.scrollRef;
|
|
34
|
-
onHandleDodging?.({
|
|
36
|
+
onHandleDodging?.({
|
|
37
|
+
liftUp: 0,
|
|
38
|
+
viewRef: viewRef || null,
|
|
39
|
+
keyboardEvent: lastKeyboardEvent.current
|
|
40
|
+
});
|
|
35
41
|
previousLift.current = undefined;
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
45
|
+
/**
|
|
46
|
+
* @param {import('react-native').KeyboardEvent | undefined} event
|
|
47
|
+
* @param {boolean} visible
|
|
48
|
+
* @param {boolean} fromIdle
|
|
49
|
+
*/
|
|
50
|
+
doDodgeKeyboard.current = (event, visible, fromIdle) => {
|
|
51
|
+
if (Platform.OS === 'ios' && event && !event?.isEventFromThisApp) return;
|
|
52
|
+
|
|
53
|
+
if (typeof visible !== 'boolean') {
|
|
54
|
+
if (typeof wasVisible.current === 'boolean') {
|
|
55
|
+
visible = wasVisible.current;
|
|
56
|
+
} else return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
wasVisible.current = visible;
|
|
60
|
+
if (event) lastKeyboardEvent.current = event;
|
|
61
|
+
|
|
40
62
|
try {
|
|
41
|
-
const keyboardInfo = Keyboard.metrics();
|
|
42
|
-
const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
|
|
63
|
+
const keyboardInfo = event?.endCoordinates || Keyboard.metrics();
|
|
43
64
|
|
|
44
65
|
// console.log('doDodgeKeyboard');
|
|
66
|
+
|
|
67
|
+
if (pendingIdleTask.current !== undefined)
|
|
68
|
+
cancelIdleCallback(pendingIdleTask.current);
|
|
69
|
+
pendingIdleTask.current = undefined;
|
|
70
|
+
|
|
45
71
|
if (
|
|
46
|
-
|
|
72
|
+
visible &&
|
|
47
73
|
keyboardInfo &&
|
|
48
74
|
!disabled &&
|
|
49
|
-
(keyboardInfo
|
|
50
|
-
keyboardInfo.height + keyboardInfo.screenY === windowHeight) &&
|
|
75
|
+
(!isOffScreenY(keyboardInfo) && !isOffScreenX(keyboardInfo)) &&
|
|
51
76
|
keyboardInfo.screenY
|
|
52
77
|
) {
|
|
53
78
|
// console.log('doDodgeKeyboard 1 entries:', Object.keys(viewRefsMap.current).length);
|
|
54
|
-
|
|
79
|
+
const itemIteList = Object.entries(viewRefsMap.current);
|
|
80
|
+
const allInputList = checkIfElementIsFocused && itemIteList.map(v =>
|
|
81
|
+
v[1].__is_standalone
|
|
82
|
+
? [v[1].scrollRef]
|
|
83
|
+
: Object.values(v[1].inputRef).map(v => v.ref)
|
|
84
|
+
).flat();
|
|
85
|
+
|
|
86
|
+
const initIdleTask = () => {
|
|
87
|
+
if (!fromIdle)
|
|
88
|
+
pendingIdleTask.current = requestIdleCallback(() => {
|
|
89
|
+
doDodgeKeyboard.current(undefined, undefined, true);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const checkFocused = checkIfElementIsFocused || (r => r?.isFocused?.());
|
|
94
|
+
|
|
95
|
+
for (const [scrollId, obj] of itemIteList) {
|
|
55
96
|
const { scrollRef, inputRef, __is_standalone, _standalone_props } = obj;
|
|
56
97
|
if (scrollRef) {
|
|
57
98
|
if (__is_standalone) {
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
99
|
+
if (checkFocused(scrollRef, allInputList)) {
|
|
100
|
+
UIManager.measure(findNodeHandle(scrollRef), (_x, _y, w, h, x, y) => {
|
|
101
|
+
const { dodge_keyboard_offset } = _standalone_props || {};
|
|
102
|
+
const thisOffset = isNumber(dodge_keyboard_offset) ? dodge_keyboard_offset : offset;
|
|
103
|
+
|
|
104
|
+
const liftUp = Math.max(0, (y - keyboardInfo.screenY) + Math.min(h + thisOffset, keyboardInfo.screenY));
|
|
105
|
+
clearPreviousDodge(scrollId);
|
|
106
|
+
if (liftUp) {
|
|
107
|
+
previousLift.current = scrollId;
|
|
108
|
+
onHandleDodging?.({
|
|
109
|
+
liftUp,
|
|
110
|
+
viewRef: scrollRef,
|
|
111
|
+
keyboardEvent: lastKeyboardEvent.current
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
initIdleTask();
|
|
115
|
+
});
|
|
71
116
|
return;
|
|
72
117
|
}
|
|
73
118
|
} else {
|
|
74
119
|
for (const { ref: inputObj, props } of Object.values(inputRef)) {
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
if (checkFocused(inputObj, allInputList)) {
|
|
121
|
+
Promise.all([
|
|
122
|
+
new Promise(resolve => {
|
|
123
|
+
UIManager.measure(findNodeHandle(scrollRef), (x, y, w, h, px, py) => {
|
|
124
|
+
resolve({ h, py });
|
|
125
|
+
});
|
|
126
|
+
}),
|
|
127
|
+
new Promise(resolve => {
|
|
128
|
+
inputObj.measure((x, y, w, h, px, py) => { // y is dynamic
|
|
129
|
+
resolve({ py });
|
|
130
|
+
});
|
|
131
|
+
}),
|
|
132
|
+
new Promise((resolve, reject) => {
|
|
78
133
|
inputObj.measureLayout(scrollRef, (l, t, w, h) => { // t is fixed
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
scrollRef.scrollTo({ y: newScrollY, animated: true });
|
|
105
|
-
setCurrentPaddedScroller();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
134
|
+
resolve({ t, h })
|
|
135
|
+
}, reject);
|
|
136
|
+
})
|
|
137
|
+
]).then(([{ h: sh, py: sy }, { py: y }, { t, h }]) => {
|
|
138
|
+
|
|
139
|
+
const { dodge_keyboard_offset } = props || {};
|
|
140
|
+
const thisOffset = isNumber(dodge_keyboard_offset) ? dodge_keyboard_offset : offset;
|
|
141
|
+
|
|
142
|
+
const scrollInputY = y - sy;
|
|
143
|
+
|
|
144
|
+
if (scrollInputY >= 0 && scrollInputY <= sh) { // is input visible in viewport
|
|
145
|
+
const clampedLift = Math.min(h + thisOffset, keyboardInfo.screenY);
|
|
146
|
+
|
|
147
|
+
if (y + clampedLift >= keyboardInfo.screenY) { // is below keyboard
|
|
148
|
+
const requiredScrollY = (t - (keyboardInfo.screenY - sy)) + clampedLift;
|
|
149
|
+
// for lifting up the scroll-view
|
|
150
|
+
const liftUp = Math.max(0, requiredScrollY - t);
|
|
151
|
+
clearPreviousDodge(scrollId);
|
|
152
|
+
if (liftUp) {
|
|
153
|
+
previousLift.current = scrollId;
|
|
154
|
+
onHandleDodging?.({
|
|
155
|
+
liftUp,
|
|
156
|
+
viewRef: scrollRef,
|
|
157
|
+
keyboardEvent: lastKeyboardEvent.current
|
|
158
|
+
});
|
|
108
159
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
160
|
+
|
|
161
|
+
const scrollLift = Math.max(0, (sy + sh + (thisOffset >= 0 ? thisOffset : 0)) - keyboardInfo.screenY);
|
|
162
|
+
const newScrollY = Math.min(requiredScrollY, t);
|
|
163
|
+
|
|
164
|
+
// console.log('scrolling-to:', requiredScrollY, ' scrollLift:', scrollLift);
|
|
165
|
+
if (scrollLift) {
|
|
166
|
+
setCurrentPaddedScroller([scrollId, scrollLift, newScrollY]);
|
|
167
|
+
} else {
|
|
168
|
+
tryPerformScroll(scrollRef, newScrollY, true);
|
|
169
|
+
setCurrentPaddedScroller();
|
|
170
|
+
}
|
|
171
|
+
initIdleTask();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}).catch(e => {
|
|
175
|
+
console.error('frame calculation error:', e);
|
|
176
|
+
});
|
|
112
177
|
return;
|
|
113
178
|
}
|
|
114
179
|
}
|
|
@@ -124,20 +189,24 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
124
189
|
}
|
|
125
190
|
}
|
|
126
191
|
|
|
192
|
+
const tryPerformScroll = (ref, y, animated = true) => {
|
|
193
|
+
if (!ref) return;
|
|
194
|
+
|
|
195
|
+
if (ref.scrollTo) {
|
|
196
|
+
ref.scrollTo?.({ y, animated });
|
|
197
|
+
} else if (ref.scrollToOffset) {
|
|
198
|
+
ref.scrollToOffset?.({ offset: y, animated });
|
|
199
|
+
} else {
|
|
200
|
+
ref.getScrollResponder?.()?.scrollTo?.({ y, animated });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
127
204
|
const [paddedId, paddedSize, paddedScroll] = currentPaddedScroller || [];
|
|
128
205
|
|
|
129
206
|
useEffect(() => {
|
|
130
207
|
if (currentPaddedScroller) {
|
|
131
208
|
const ref = viewRefsMap.current[paddedId]?.scrollRef;
|
|
132
|
-
|
|
133
|
-
if (ref.scrollTo) {
|
|
134
|
-
ref.scrollTo?.({ y: paddedScroll, animated: true });
|
|
135
|
-
} else if (ref.scrollToOffset) {
|
|
136
|
-
ref.scrollToOffset?.({ offset: paddedScroll, animated: true });
|
|
137
|
-
} else {
|
|
138
|
-
ref.getScrollResponder?.()?.scrollTo?.({ y: paddedScroll, animated: true });
|
|
139
|
-
}
|
|
140
|
-
}
|
|
209
|
+
tryPerformScroll(ref, paddedScroll, false);
|
|
141
210
|
}
|
|
142
211
|
}, [currentPaddedScroller]);
|
|
143
212
|
|
|
@@ -147,19 +216,15 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
147
216
|
|
|
148
217
|
useEffect(() => {
|
|
149
218
|
if (disabled) return;
|
|
150
|
-
const frameListener = Keyboard.addListener('keyboardDidChangeFrame', e =>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const hiddenListener = Keyboard.addListener('keyboardDidHide', e => {
|
|
160
|
-
isKeyboardVisible.current = false;
|
|
161
|
-
doDodgeKeyboard.current();
|
|
162
|
-
});
|
|
219
|
+
const frameListener = Keyboard.addListener('keyboardDidChangeFrame', e => doDodgeKeyboard.current(e));
|
|
220
|
+
const showListener = Keyboard.addListener(
|
|
221
|
+
Platform.OS === 'android' ? 'keyboardDidShow' : 'keyboardWillShow',
|
|
222
|
+
e => doDodgeKeyboard.current(e, true)
|
|
223
|
+
);
|
|
224
|
+
const hiddenListener = Keyboard.addListener(
|
|
225
|
+
Platform.OS === 'android' ? 'keyboardDidHide' : 'keyboardWillHide',
|
|
226
|
+
e => doDodgeKeyboard.current(e, false)
|
|
227
|
+
);
|
|
163
228
|
|
|
164
229
|
return () => {
|
|
165
230
|
frameListener.remove();
|
|
@@ -168,145 +233,164 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
168
233
|
}
|
|
169
234
|
}, [!disabled]);
|
|
170
235
|
|
|
171
|
-
|
|
172
|
-
<ReactHijacker
|
|
173
|
-
doHijack={(node, path) => {
|
|
174
|
-
if (node?.props?.['dodge_keyboard_scan_off']) return { element: node };
|
|
175
|
-
|
|
176
|
-
const isStandalone = isDodgeInput(node);
|
|
177
|
-
|
|
178
|
-
if (isStandalone || isDodgeScrollable(node, disableTagCheck)) {
|
|
179
|
-
const scrollId = path.join('=>');
|
|
180
|
-
const initNode = () => {
|
|
181
|
-
if (!viewRefsMap.current[scrollId])
|
|
182
|
-
viewRefsMap.current[scrollId] = { inputRef: {} };
|
|
183
|
-
|
|
184
|
-
if (isStandalone) {
|
|
185
|
-
viewRefsMap.current[scrollId].__is_standalone = true;
|
|
186
|
-
viewRefsMap.current[scrollId]._standalone_props = {
|
|
187
|
-
dodge_keyboard_offset: node.props?.dodge_keyboard_offset,
|
|
188
|
-
dodge_keyboard_lift: node.props?.dodge_keyboard_lift
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
const shouldPad = scrollId === paddedId;
|
|
193
|
-
const contentStyle = shouldPad && StyleSheet.flatten(node.props?.contentContainerStyle);
|
|
194
|
-
const rootRenderItem = node.prop?.renderItem;
|
|
195
|
-
const rootKeyExtractor = node.prop?.keyExtractor;
|
|
196
|
-
const hasInternalList = !isStandalone && (typeof rootRenderItem === 'function' && !node.props?.children);
|
|
197
|
-
|
|
198
|
-
const doRefCleanup = () => {
|
|
199
|
-
if (
|
|
200
|
-
viewRefsMap.current[scrollId]?.scrollRef ||
|
|
201
|
-
Object.keys(viewRefsMap.current[scrollId]?.inputRef || {}).length
|
|
202
|
-
) return;
|
|
203
|
-
delete viewRefsMap.current[scrollId];
|
|
204
|
-
}
|
|
236
|
+
const nodeIdIte = useRef(0);
|
|
205
237
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
children,
|
|
209
|
-
path: childPath,
|
|
210
|
-
doHijack: (inputNode, path) => {
|
|
211
|
-
if (isDodgeInput(inputNode, disableTagCheck)) {
|
|
212
|
-
const inputId = path.join('=>');
|
|
213
|
-
const initInputNode = () => {
|
|
214
|
-
initNode();
|
|
215
|
-
if (!viewRefsMap.current[scrollId].inputRef[inputId])
|
|
216
|
-
viewRefsMap.current[scrollId].inputRef[inputId] = {};
|
|
217
|
-
viewRefsMap.current[scrollId].inputRef[inputId].props = {
|
|
218
|
-
dodge_keyboard_offset: inputNode.props?.dodge_keyboard_offset,
|
|
219
|
-
dodge_keyboard_lift: inputNode.props?.dodge_keyboard_lift
|
|
220
|
-
};
|
|
221
|
-
}
|
|
238
|
+
const onHijackNode = node => {
|
|
239
|
+
if (offDodgeScan(node)) return createHijackedElement(node);
|
|
222
240
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
props: {
|
|
227
|
-
...inputNode.props,
|
|
228
|
-
onFocus: (...args) => {
|
|
229
|
-
doDodgeKeyboard.current();
|
|
230
|
-
return inputNode.props?.onFocus?.(...args);
|
|
231
|
-
},
|
|
232
|
-
onLayout: (...args) => {
|
|
233
|
-
doDodgeKeyboard.current();
|
|
234
|
-
return inputNode.props?.onLayout?.(...args);
|
|
235
|
-
},
|
|
236
|
-
ref: r => {
|
|
237
|
-
if (r) {
|
|
238
|
-
initInputNode();
|
|
239
|
-
|
|
240
|
-
viewRefsMap.current[scrollId].inputRef[inputId].ref = r;
|
|
241
|
-
} else if (viewRefsMap.current[scrollId]?.inputRef?.[inputId]) {
|
|
242
|
-
delete viewRefsMap.current[scrollId].inputRef[inputId];
|
|
243
|
-
doRefCleanup();
|
|
244
|
-
}
|
|
241
|
+
const isStandalone = isDodgeInput(node);
|
|
242
|
+
if (!isStandalone && !isDodgeScrollable(node, disableTagCheck)) return;
|
|
245
243
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
thatRef(r);
|
|
249
|
-
} else if (thatRef) thatRef.current = r;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
});
|
|
244
|
+
const renderer = () => {
|
|
245
|
+
const scrollId = useMemo(() => `${++nodeIdIte.current}`, []);
|
|
256
246
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
...shouldPad ? {
|
|
261
|
-
contentContainerStyle: {
|
|
262
|
-
...contentStyle,
|
|
263
|
-
paddingBottom: paddedSize + (isNumber(contentStyle?.paddingBottom) ? contentStyle.paddingBottom : 0)
|
|
264
|
-
}
|
|
265
|
-
} : {},
|
|
266
|
-
ref: r => {
|
|
267
|
-
if (r) {
|
|
268
|
-
initNode();
|
|
269
|
-
viewRefsMap.current[scrollId].scrollRef = r;
|
|
270
|
-
} else if (viewRefsMap.current[scrollId]) {
|
|
271
|
-
viewRefsMap.current[scrollId].scrollRef = undefined;
|
|
272
|
-
doRefCleanup();
|
|
273
|
-
}
|
|
247
|
+
const initNode = () => {
|
|
248
|
+
if (!viewRefsMap.current[scrollId])
|
|
249
|
+
viewRefsMap.current[scrollId] = { inputRef: {} };
|
|
274
250
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
251
|
+
if (isStandalone) {
|
|
252
|
+
viewRefsMap.current[scrollId].__is_standalone = true;
|
|
253
|
+
viewRefsMap.current[scrollId]._standalone_props = {
|
|
254
|
+
dodge_keyboard_offset: node.props?.dodge_keyboard_offset,
|
|
255
|
+
dodge_keyboard_lift: node.props?.dodge_keyboard_lift
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const shouldPad = !isStandalone && scrollId === paddedId;
|
|
260
|
+
const contentStyle = shouldPad && StyleSheet.flatten(node.props?.contentContainerStyle);
|
|
261
|
+
const rootRenderItem = node.props?.renderItem;
|
|
262
|
+
const hasInternalList = !isStandalone && (typeof rootRenderItem === 'function' && !node.props?.children);
|
|
263
|
+
|
|
264
|
+
const doRefCleanup = () => {
|
|
265
|
+
if (
|
|
266
|
+
viewRefsMap.current[scrollId]?.scrollRef ||
|
|
267
|
+
Object.keys(viewRefsMap.current[scrollId]?.inputRef || {}).length
|
|
268
|
+
) return;
|
|
269
|
+
delete viewRefsMap.current[scrollId];
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const injectChild = inputNode => {
|
|
273
|
+
if (offDodgeScan(inputNode)) return createHijackedElement(inputNode);
|
|
274
|
+
|
|
275
|
+
if (!isDodgeInput(inputNode, disableTagCheck)) return;
|
|
276
|
+
|
|
277
|
+
const inputRenderer = () => {
|
|
278
|
+
const inputId = useMemo(() => `${++nodeIdIte.current}`, []);
|
|
279
|
+
const initInputNode = () => {
|
|
280
|
+
initNode();
|
|
281
|
+
if (!viewRefsMap.current[scrollId].inputRef[inputId])
|
|
282
|
+
viewRefsMap.current[scrollId].inputRef[inputId] = {};
|
|
283
|
+
viewRefsMap.current[scrollId].inputRef[inputId].props = {
|
|
284
|
+
dodge_keyboard_offset: inputNode.props?.dodge_keyboard_offset,
|
|
285
|
+
dodge_keyboard_lift: inputNode.props?.dodge_keyboard_lift
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
initInputNode();
|
|
290
|
+
|
|
291
|
+
const newProps = {
|
|
292
|
+
...inputNode.props,
|
|
293
|
+
__dodging_keyboard: true,
|
|
294
|
+
onFocus: (...args) => {
|
|
295
|
+
doDodgeKeyboard.current();
|
|
296
|
+
return inputNode.props?.onFocus?.(...args);
|
|
297
|
+
},
|
|
298
|
+
onLayout: (...args) => {
|
|
299
|
+
doDodgeKeyboard.current();
|
|
300
|
+
return inputNode.props?.onLayout?.(...args);
|
|
301
|
+
},
|
|
302
|
+
ref: r => {
|
|
303
|
+
if (r) {
|
|
304
|
+
initInputNode();
|
|
305
|
+
|
|
306
|
+
viewRefsMap.current[scrollId].inputRef[inputId].ref = r;
|
|
307
|
+
} else if (viewRefsMap.current[scrollId]?.inputRef?.[inputId]) {
|
|
308
|
+
delete viewRefsMap.current[scrollId].inputRef[inputId];
|
|
309
|
+
doRefCleanup();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const thatRef = inputNode.props?.ref;
|
|
313
|
+
if (typeof thatRef === 'function') {
|
|
314
|
+
thatRef(r);
|
|
315
|
+
} else if (thatRef) thatRef.current = r;
|
|
306
316
|
}
|
|
307
317
|
};
|
|
318
|
+
|
|
319
|
+
return cloneElement(inputNode, newProps);
|
|
308
320
|
}
|
|
309
|
-
|
|
321
|
+
|
|
322
|
+
return createHijackedElement(
|
|
323
|
+
<__HijackNode>
|
|
324
|
+
{inputRenderer}
|
|
325
|
+
</__HijackNode>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const newProps = {
|
|
330
|
+
...node.props,
|
|
331
|
+
__dodging_keyboard: true,
|
|
332
|
+
...shouldPad ? {
|
|
333
|
+
contentContainerStyle: {
|
|
334
|
+
...contentStyle,
|
|
335
|
+
paddingBottom: paddedSize + (isNumber(contentStyle?.paddingBottom) ? contentStyle.paddingBottom : 0)
|
|
336
|
+
}
|
|
337
|
+
} : {},
|
|
338
|
+
ref: r => {
|
|
339
|
+
if (r) {
|
|
340
|
+
initNode();
|
|
341
|
+
viewRefsMap.current[scrollId].scrollRef = r;
|
|
342
|
+
} else if (viewRefsMap.current[scrollId]) {
|
|
343
|
+
viewRefsMap.current[scrollId].scrollRef = undefined;
|
|
344
|
+
doRefCleanup();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const thatRef = node.props?.ref;
|
|
348
|
+
if (typeof thatRef === 'function') {
|
|
349
|
+
thatRef(r);
|
|
350
|
+
} else if (thatRef) thatRef.current = r;
|
|
351
|
+
},
|
|
352
|
+
...isStandalone ? {
|
|
353
|
+
onFocus: (...args) => {
|
|
354
|
+
doDodgeKeyboard.current();
|
|
355
|
+
return node.props?.onFocus?.(...args);
|
|
356
|
+
}
|
|
357
|
+
} : {},
|
|
358
|
+
onLayout: (...args) => {
|
|
359
|
+
doDodgeKeyboard.current();
|
|
360
|
+
return node.props?.onLayout?.(...args);
|
|
361
|
+
},
|
|
362
|
+
...isStandalone ? {} :
|
|
363
|
+
hasInternalList ? {
|
|
364
|
+
renderItem: (...args) => {
|
|
365
|
+
return (
|
|
366
|
+
<ReactHijacker
|
|
367
|
+
doHijack={injectChild}>
|
|
368
|
+
{rootRenderItem(...args)}
|
|
369
|
+
</ReactHijacker>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
} : {
|
|
373
|
+
children:
|
|
374
|
+
ReactHijacker({
|
|
375
|
+
children: node.props?.children,
|
|
376
|
+
doHijack: injectChild
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return cloneElement(node, newProps);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
return createHijackedElement(
|
|
385
|
+
<__HijackNode>
|
|
386
|
+
{renderer}
|
|
387
|
+
</__HijackNode>
|
|
388
|
+
);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
return (
|
|
392
|
+
<ReactHijacker
|
|
393
|
+
doHijack={onHijackNode}>
|
|
310
394
|
{children}
|
|
311
395
|
</ReactHijacker>
|
|
312
396
|
);
|
|
@@ -329,62 +413,43 @@ const REACT_SYMBOLS = {
|
|
|
329
413
|
memo: Symbol.for('react.memo')
|
|
330
414
|
};
|
|
331
415
|
|
|
332
|
-
export function ReactHijacker({ children, doHijack,
|
|
333
|
-
const renderRefs = useMemo(() => new Map(), []);
|
|
334
|
-
|
|
416
|
+
export function ReactHijacker({ children, doHijack, enableLocator }) {
|
|
335
417
|
const instantDoHijack = useRef();
|
|
336
418
|
instantDoHijack.current = doHijack;
|
|
337
419
|
|
|
338
|
-
const injectIntoTree = (node, path
|
|
420
|
+
const injectIntoTree = (node, path) => {
|
|
339
421
|
if (!node) return node;
|
|
340
422
|
if (Array.isArray(node)) {
|
|
341
|
-
|
|
342
|
-
return Children.map(node, (v, i) => injectIntoTree(v, path, i));
|
|
423
|
+
return Children.map(node, (v, i) => injectIntoTree(v, path && [...path, i]));
|
|
343
424
|
}
|
|
344
425
|
if (!isValidElement(node)) return node;
|
|
345
426
|
|
|
346
|
-
path = [...path,
|
|
427
|
+
if (path) path = [...path, getNodeId(node)];
|
|
347
428
|
|
|
348
429
|
let thisObj;
|
|
349
|
-
if (thisObj = instantDoHijack.current?.(node, path)) {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
if (Object.hasOwn(thisObj, 'element')) return element;
|
|
353
|
-
if (props) return cloneElement(node, props);
|
|
354
|
-
return node;
|
|
430
|
+
if (Object.hasOwn((thisObj = instantDoHijack.current?.(node, path)) || {}, '__element')) {
|
|
431
|
+
return thisObj.__element;
|
|
355
432
|
}
|
|
356
433
|
|
|
357
434
|
if (!isHostElement(node)) {
|
|
358
435
|
const wrapNodeType = (nodeType, pathway, pathKey) => {
|
|
359
|
-
pathway = [...pathway, getNodeId(undefined, nodeType, pathKey)];
|
|
360
|
-
const path_id = pathway.join(',');
|
|
361
|
-
let renderRefStore = renderRefs.get(nodeType);
|
|
362
|
-
|
|
363
|
-
if (renderRefStore?.[path_id]) return renderRefStore[path_id];
|
|
436
|
+
if (pathway) pathway = [...pathway, getNodeId(undefined, nodeType, pathKey)];
|
|
364
437
|
|
|
365
438
|
// if (doLogging) console.log('wrapNodeType path:', pathway, ' node:', nodeType);
|
|
366
439
|
const render = (renderedNode) => {
|
|
367
440
|
// if (doLogging) console.log('deep path:', pathway, ' node:', renderedNode);
|
|
368
|
-
return injectIntoTree(renderedNode, pathway);
|
|
441
|
+
return injectIntoTree(renderedNode, pathway && [...pathway, 0]);
|
|
369
442
|
}
|
|
370
443
|
|
|
371
|
-
let newType;
|
|
372
|
-
|
|
373
444
|
if (typeof nodeType === 'function') { // check self closed tag
|
|
374
|
-
|
|
445
|
+
return hijackRender(nodeType, render);
|
|
375
446
|
} else if (nodeType?.$$typeof === REACT_SYMBOLS.forwardRef) {
|
|
376
|
-
|
|
447
|
+
return forwardRef(hijackRender(nodeType.render, render));
|
|
377
448
|
} else if (nodeType?.$$typeof === REACT_SYMBOLS.memo) {
|
|
378
|
-
newType = memo(wrapNodeType(nodeType.type, pathway), nodeType.compare);
|
|
449
|
+
const newType = memo(wrapNodeType(nodeType.type, pathway), nodeType.compare);
|
|
379
450
|
newType.displayName = nodeType.displayName || nodeType.name;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (newType) {
|
|
383
|
-
if (!renderRefStore) renderRefs.set(nodeType, renderRefStore = {});
|
|
384
|
-
renderRefStore[path_id] = newType;
|
|
385
451
|
return newType;
|
|
386
452
|
}
|
|
387
|
-
|
|
388
453
|
return nodeType;
|
|
389
454
|
}
|
|
390
455
|
|
|
@@ -394,15 +459,24 @@ export function ReactHijacker({ children, doHijack, path }) {
|
|
|
394
459
|
node.type?.$$typeof === REACT_SYMBOLS.memo // check memo
|
|
395
460
|
) {
|
|
396
461
|
// if (doLogging) console.log('doLog path:', path, ' node:', node);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
462
|
+
return (
|
|
463
|
+
<__HijackNodePath>
|
|
464
|
+
{() => {
|
|
465
|
+
const hijackType = useMemo(() =>
|
|
466
|
+
wrapNodeType(node.type, path && path.slice(0, -1), node.key),
|
|
467
|
+
[node.type]
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
return createElement(
|
|
471
|
+
hijackType,
|
|
472
|
+
{
|
|
473
|
+
...node.props,
|
|
474
|
+
key: node.key
|
|
475
|
+
},
|
|
476
|
+
node.props?.children
|
|
477
|
+
);
|
|
478
|
+
}}
|
|
479
|
+
</__HijackNodePath>
|
|
406
480
|
);
|
|
407
481
|
}
|
|
408
482
|
}
|
|
@@ -410,15 +484,24 @@ export function ReactHijacker({ children, doHijack, path }) {
|
|
|
410
484
|
const children = node.props?.children;
|
|
411
485
|
if (children)
|
|
412
486
|
return cloneElement(node, {
|
|
413
|
-
children: injectIntoTree(children, path)
|
|
487
|
+
children: injectIntoTree(children, path && [...path, 0])
|
|
414
488
|
});
|
|
415
489
|
|
|
416
490
|
return node;
|
|
417
491
|
};
|
|
418
492
|
|
|
419
|
-
return injectIntoTree(children,
|
|
493
|
+
return injectIntoTree(children, enableLocator ? [] : undefined);
|
|
420
494
|
};
|
|
421
495
|
|
|
496
|
+
export const createHijackedElement = (element) => ({ __element: element });
|
|
497
|
+
export function __HijackNode({ children }) {
|
|
498
|
+
return children?.();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function __HijackNodePath({ children }) {
|
|
502
|
+
return children?.();
|
|
503
|
+
}
|
|
504
|
+
|
|
422
505
|
const hijackRender = (type, doHijack) =>
|
|
423
506
|
new Proxy(type, {
|
|
424
507
|
apply(target, thisArg, args) {
|
|
@@ -474,6 +557,8 @@ export function isHostElement(node) {
|
|
|
474
557
|
);
|
|
475
558
|
}
|
|
476
559
|
|
|
560
|
+
const offDodgeScan = (node) => node?.props?.dodge_keyboard_scan_off || node?.props?.__dodging_keyboard;
|
|
561
|
+
|
|
477
562
|
export const isDodgeScrollable = (element, disableTagCheck) => {
|
|
478
563
|
if (element?.props?.['dodge_keyboard_scrollable']) return true;
|
|
479
564
|
if (!element?.type || element?.props?.horizontal || disableTagCheck) return false;
|
|
@@ -492,4 +577,83 @@ export const isDodgeInput = (element, disableTagCheck) => {
|
|
|
492
577
|
|
|
493
578
|
return inputTypes.includes(element.type?.displayName)
|
|
494
579
|
|| inputTypes.includes(element.type?.name);
|
|
495
|
-
};
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
const isOffScreenY = (keyboardInfo, minimumThreshold = .27) =>
|
|
583
|
+
!keyboardInfo ||
|
|
584
|
+
keyboardInfo.height <= 0 ||
|
|
585
|
+
((keyboardInfo.screenY / (keyboardInfo.screenY + keyboardInfo.height)) < .40) ||
|
|
586
|
+
(keyboardInfo.screenY / Dimensions.get('window').height) < minimumThreshold;
|
|
587
|
+
|
|
588
|
+
const isOffScreenX = (keyboardInfo, minimumThreshold = .7) => {
|
|
589
|
+
const vw = Dimensions.get('window').width;
|
|
590
|
+
|
|
591
|
+
return !keyboardInfo ||
|
|
592
|
+
(Math.min(keyboardInfo.width, vw) / Math.max(keyboardInfo.width, vw)) < minimumThreshold;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export const KeyboardPlaceholderView = ({ doHeight }) => {
|
|
596
|
+
const height = useAnimatedValue(0);
|
|
597
|
+
|
|
598
|
+
const instantDoHeight = useRef();
|
|
599
|
+
instantDoHeight.current = doHeight;
|
|
600
|
+
|
|
601
|
+
useEffect(() => {
|
|
602
|
+
let wasVisible;
|
|
603
|
+
/**
|
|
604
|
+
* @param {import('react-native').KeyboardEvent} event
|
|
605
|
+
* @param {boolean} visible
|
|
606
|
+
*/
|
|
607
|
+
const updateKeyboardHeight = (event, visible) => {
|
|
608
|
+
if (typeof visible !== 'boolean') {
|
|
609
|
+
if (typeof wasVisible === 'boolean') {
|
|
610
|
+
visible = wasVisible;
|
|
611
|
+
} else return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
wasVisible = visible;
|
|
615
|
+
|
|
616
|
+
const { endCoordinates, isEventFromThisApp, duration } = event;
|
|
617
|
+
if (Platform.OS === 'ios' && !isEventFromThisApp) return;
|
|
618
|
+
|
|
619
|
+
const kh = (visible && !isOffScreenX(endCoordinates) && !isOffScreenY(endCoordinates, .3))
|
|
620
|
+
? Math.max(Dimensions.get('window').height - endCoordinates.screenY, 0)
|
|
621
|
+
: 0;
|
|
622
|
+
|
|
623
|
+
const newHeight = Math.max(0, instantDoHeight.current ? instantDoHeight.current(kh) : kh);
|
|
624
|
+
const newDuration = (Math.abs(height._value - newHeight) * duration) / Math.max(0, endCoordinates.height);
|
|
625
|
+
|
|
626
|
+
Animated.timing(height, {
|
|
627
|
+
duration: newDuration || 0,
|
|
628
|
+
toValue: newHeight,
|
|
629
|
+
useNativeDriver: false
|
|
630
|
+
}).start();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const initialMetric = Keyboard.metrics();
|
|
634
|
+
if (initialMetric)
|
|
635
|
+
updateKeyboardHeight({
|
|
636
|
+
endCoordinates: initialMetric,
|
|
637
|
+
isEventFromThisApp: true,
|
|
638
|
+
duration: 0
|
|
639
|
+
}, true);
|
|
640
|
+
|
|
641
|
+
const frameListener = Keyboard.addListener('keyboardDidChangeFrame', e => updateKeyboardHeight(e));
|
|
642
|
+
const showListener = Keyboard.addListener(
|
|
643
|
+
Platform.OS === 'android' ? 'keyboardDidShow' : 'keyboardWillShow',
|
|
644
|
+
e => updateKeyboardHeight(e, true)
|
|
645
|
+
);
|
|
646
|
+
const hiddenListener = Keyboard.addListener(
|
|
647
|
+
Platform.OS === 'android' ? 'keyboardDidHide' : 'keyboardWillHide',
|
|
648
|
+
e => updateKeyboardHeight(e, false)
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
return () => {
|
|
652
|
+
frameListener.remove();
|
|
653
|
+
showListener.remove();
|
|
654
|
+
hiddenListener.remove();
|
|
655
|
+
}
|
|
656
|
+
}, []);
|
|
657
|
+
|
|
658
|
+
return <Animated.View style={{ height }} />;
|
|
659
|
+
}
|
package/package.json
CHANGED
package/TODO
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
keyboard not dodging in state changes
|