react-native-dodge-keyboard 1.0.0 → 1.0.2
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/TODO +1 -0
- package/index.d.ts +22 -15
- package/index.js +355 -166
- package/package.json +1 -1
package/TODO
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
keyboard not dodging in state changes
|
package/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { ScrollView } from "react-native";
|
|
2
|
+
import { ScrollView, View } from "react-native";
|
|
3
3
|
|
|
4
4
|
export interface LiftUpDodge {
|
|
5
5
|
/**
|
|
@@ -9,10 +9,11 @@ export interface LiftUpDodge {
|
|
|
9
9
|
liftUp: number;
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* A reference to the
|
|
13
|
-
*
|
|
12
|
+
* A reference to the view that should be lifted.
|
|
13
|
+
*
|
|
14
|
+
* null is returned if the view has been recently removed from the node hierarchy
|
|
14
15
|
*/
|
|
15
|
-
viewRef: ScrollView;
|
|
16
|
+
viewRef: ScrollView | View | null;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
export interface DodgeKeyboardProps {
|
|
@@ -49,22 +50,25 @@ export interface DodgeKeyboardProps {
|
|
|
49
50
|
* - "VirtualizedList"
|
|
50
51
|
*
|
|
51
52
|
* If you want a custom scrollable element to support dodging,
|
|
52
|
-
* add the prop: `
|
|
53
|
+
* add the prop: `dodge_keyboard_scrollable={true}`.
|
|
53
54
|
*
|
|
54
55
|
* By default, "TextInput" is the only known input tag.
|
|
55
56
|
* To enable dodging for a custom input element,
|
|
56
|
-
* add the prop: `
|
|
57
|
+
* add the prop: `dodge_keyboard_input={true}`.
|
|
58
|
+
*
|
|
59
|
+
* Input elements or views with dodge_keyboard_input={true} that are not inside a scrollable view must be manually lifted by responding to the `onHandleDodging` callback.
|
|
57
60
|
*/
|
|
58
61
|
disableTagCheck?: boolean;
|
|
59
62
|
|
|
60
63
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
64
|
+
* an handler used internally for checking if a view is focused
|
|
65
|
+
*
|
|
66
|
+
* @default
|
|
67
|
+
* ```js
|
|
68
|
+
* r => r?.isFocused?.()
|
|
69
|
+
* ```
|
|
66
70
|
*/
|
|
67
|
-
|
|
71
|
+
checkIfElementIsFocused?: (ref: View) => boolean;
|
|
68
72
|
|
|
69
73
|
/**
|
|
70
74
|
* Child element(s) wrapped by the dodge container.
|
|
@@ -82,9 +86,9 @@ export default function DodgeKeyboard(
|
|
|
82
86
|
|
|
83
87
|
interface doHijackResult {
|
|
84
88
|
/**
|
|
85
|
-
*
|
|
89
|
+
* element to be replace with, providing this basically ignores the `props` field
|
|
86
90
|
*/
|
|
87
|
-
|
|
91
|
+
element?: boolean;
|
|
88
92
|
/**
|
|
89
93
|
* props injected into the hijacked node.
|
|
90
94
|
*/
|
|
@@ -97,4 +101,7 @@ interface ReactHijackerProps {
|
|
|
97
101
|
children?: React.ReactNode;
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
export function ReactHijacker(props: ReactHijackerProps): React.ReactElement | null;
|
|
104
|
+
export function ReactHijacker(props: ReactHijackerProps): React.ReactElement | null;
|
|
105
|
+
|
|
106
|
+
export function isDodgeScrollable(element: React.ReactNode, disableTagCheck?: boolean): boolean;
|
|
107
|
+
export function isDodgeInput(element: React.ReactNode, disableTagCheck?: boolean): boolean;
|
package/index.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Dimensions, Keyboard, StyleSheet } from "react-native";
|
|
1
|
+
import { Children, cloneElement, createElement, forwardRef, isValidElement, memo, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Dimensions, findNodeHandle, Keyboard, StyleSheet, UIManager } from "react-native";
|
|
3
3
|
|
|
4
|
-
export default function ({ children, offset = 10, disabled, onHandleDodging, disableTagCheck,
|
|
4
|
+
export default function ({ children, offset = 10, disabled, onHandleDodging, disableTagCheck, checkIfElementIsFocused }) {
|
|
5
|
+
if (checkIfElementIsFocused !== undefined) {
|
|
6
|
+
if (typeof checkIfElementIsFocused !== 'function')
|
|
7
|
+
throw 'checkIfElementIsFocused should be a function';
|
|
5
8
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
checkIfElementIsFocused = niceFunction(checkIfElementIsFocused, 'checkIfElementIsFocused');
|
|
10
|
+
} else checkIfElementIsFocused = r => r?.isFocused?.();
|
|
11
|
+
|
|
12
|
+
if (onHandleDodging !== undefined) {
|
|
13
|
+
if (typeof onHandleDodging !== 'function')
|
|
14
|
+
throw 'onHandleDodging should be a function';
|
|
15
|
+
|
|
16
|
+
onHandleDodging = niceFunction(onHandleDodging, 'onHandleDodging');
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
if (!isNumber(offset)) throw `offset must be a valid number but got ${offset}`;
|
|
@@ -13,79 +21,106 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
13
21
|
const [currentPaddedScroller, setCurrentPaddedScroller] = useState();
|
|
14
22
|
|
|
15
23
|
/**
|
|
16
|
-
* @type {import("react").RefObject<{[key: string]: { scrollRef: import("react-native").ScrollView, inputRef: {[key: string]: import("react-native").TextInput
|
|
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 } }} }}>}
|
|
17
25
|
*/
|
|
18
26
|
const viewRefsMap = useRef({});
|
|
19
27
|
const isKeyboardVisible = useRef();
|
|
20
|
-
const
|
|
28
|
+
const doDodgeKeyboard = useRef();
|
|
21
29
|
const previousLift = useRef();
|
|
22
30
|
|
|
23
31
|
const clearPreviousDodge = (scrollId) => {
|
|
24
32
|
if (previousLift.current && previousLift.current !== scrollId) {
|
|
25
33
|
const viewRef = viewRefsMap.current[previousLift.current]?.scrollRef;
|
|
26
|
-
|
|
34
|
+
onHandleDodging?.({ liftUp: 0, viewRef: viewRef || null });
|
|
27
35
|
previousLift.current = undefined;
|
|
28
36
|
}
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
39
|
+
doDodgeKeyboard.current = () => {
|
|
40
|
+
try {
|
|
41
|
+
const keyboardInfo = Keyboard.metrics();
|
|
42
|
+
const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
|
|
43
|
+
|
|
44
|
+
// console.log('doDodgeKeyboard');
|
|
45
|
+
if (
|
|
46
|
+
isKeyboardVisible.current &&
|
|
47
|
+
keyboardInfo &&
|
|
48
|
+
!disabled &&
|
|
49
|
+
(keyboardInfo.width === windowWidth ||
|
|
50
|
+
keyboardInfo.height + keyboardInfo.screenY === windowHeight) &&
|
|
51
|
+
keyboardInfo.screenY
|
|
52
|
+
) {
|
|
53
|
+
// console.log('doDodgeKeyboard 1 entries:', Object.keys(viewRefsMap.current).length);
|
|
54
|
+
for (const [scrollId, obj] of Object.entries(viewRefsMap.current)) {
|
|
55
|
+
const { scrollRef, inputRef, __is_standalone, _standalone_props } = obj;
|
|
56
|
+
if (scrollRef) {
|
|
57
|
+
if (__is_standalone) {
|
|
58
|
+
if (checkIfElementIsFocused(scrollRef)) {
|
|
59
|
+
if (scrollRef.measureInWindow)
|
|
60
|
+
UIManager.measureInWindow(findNodeHandle(scrollRef), (x, y, w, h) => {
|
|
61
|
+
const { dodge_keyboard_offset } = _standalone_props || {};
|
|
62
|
+
const thisOffset = isNumber(dodge_keyboard_offset) ? dodge_keyboard_offset : offset;
|
|
63
|
+
|
|
64
|
+
const liftUp = Math.max(0, (y - keyboardInfo.screenY) + Math.min(h + thisOffset, keyboardInfo.screenY));
|
|
65
|
+
clearPreviousDodge(scrollId);
|
|
66
|
+
if (liftUp) {
|
|
67
|
+
previousLift.current = scrollId;
|
|
68
|
+
onHandleDodging?.({ liftUp, viewRef: scrollRef });
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
for (const { ref: inputObj, props } of Object.values(inputRef)) {
|
|
75
|
+
if (checkIfElementIsFocused(inputObj)) {
|
|
76
|
+
UIManager.measureInWindow(findNodeHandle(scrollRef), ((sx, sy, sw, sh) => {
|
|
77
|
+
inputObj.measureInWindow((x, y) => { // y is dynamic
|
|
78
|
+
inputObj.measureLayout(scrollRef, (l, t, w, h) => { // t is fixed
|
|
79
|
+
const { dodge_keyboard_offset } = props || {};
|
|
80
|
+
const thisOffset = isNumber(dodge_keyboard_offset) ? dodge_keyboard_offset : offset;
|
|
81
|
+
|
|
82
|
+
const scrollInputY = y - sy;
|
|
83
|
+
|
|
84
|
+
if (scrollInputY >= 0 && scrollInputY <= sh) { // is input visible in viewport
|
|
85
|
+
const clampedLift = Math.min(h + thisOffset, keyboardInfo.screenY);
|
|
86
|
+
|
|
87
|
+
if (y + clampedLift >= keyboardInfo.screenY) { // is below keyboard
|
|
88
|
+
const requiredScrollY = (t - (keyboardInfo.screenY - sy)) + clampedLift;
|
|
89
|
+
// for lifting up the scroll-view
|
|
90
|
+
const liftUp = Math.max(0, requiredScrollY - t);
|
|
91
|
+
clearPreviousDodge(scrollId);
|
|
92
|
+
if (liftUp) {
|
|
93
|
+
previousLift.current = scrollId;
|
|
94
|
+
onHandleDodging?.({ liftUp, viewRef: scrollRef });
|
|
95
|
+
}
|
|
65
96
|
|
|
66
|
-
|
|
67
|
-
|
|
97
|
+
const scrollLift = Math.max(0, (sy + sh + (thisOffset >= 0 ? thisOffset : 0)) - keyboardInfo.screenY);
|
|
98
|
+
const newScrollY = Math.min(requiredScrollY, t);
|
|
68
99
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
100
|
+
// console.log('scrolling-to:', requiredScrollY, ' scrollLift:', scrollLift);
|
|
101
|
+
if (scrollLift) {
|
|
102
|
+
setCurrentPaddedScroller([scrollId, scrollLift, newScrollY]);
|
|
103
|
+
} else {
|
|
104
|
+
scrollRef.scrollTo({ y: newScrollY, animated: true });
|
|
105
|
+
setCurrentPaddedScroller();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
75
108
|
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
82
115
|
}
|
|
83
116
|
}
|
|
84
117
|
}
|
|
118
|
+
} else {
|
|
119
|
+
setCurrentPaddedScroller();
|
|
120
|
+
clearPreviousDodge();
|
|
85
121
|
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
setCurrentPaddedScroller();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('doDodgeKeyboard err:', error);
|
|
89
124
|
}
|
|
90
125
|
}
|
|
91
126
|
|
|
@@ -93,35 +128,37 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
93
128
|
|
|
94
129
|
useEffect(() => {
|
|
95
130
|
if (currentPaddedScroller) {
|
|
96
|
-
viewRefsMap.current[paddedId]
|
|
131
|
+
const ref = viewRefsMap.current[paddedId]?.scrollRef;
|
|
132
|
+
if (ref) {
|
|
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
|
+
}
|
|
97
141
|
}
|
|
98
142
|
}, [currentPaddedScroller]);
|
|
99
143
|
|
|
100
144
|
useEffect(() => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.error('onDodgeKeyboard err:', error);
|
|
105
|
-
}
|
|
106
|
-
}, [offset, !disabled, !forceDodgeFocusId]);
|
|
145
|
+
doDodgeKeyboard.current();
|
|
146
|
+
}, [offset, !disabled]);
|
|
107
147
|
|
|
108
148
|
useEffect(() => {
|
|
109
|
-
if (disabled)
|
|
110
|
-
isKeyboardVisible.current = false;
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
149
|
+
if (disabled) return;
|
|
113
150
|
const frameListener = Keyboard.addListener('keyboardDidChangeFrame', e => {
|
|
114
|
-
|
|
151
|
+
doDodgeKeyboard.current();
|
|
115
152
|
});
|
|
116
153
|
|
|
117
154
|
const showListener = Keyboard.addListener('keyboardDidShow', e => {
|
|
118
155
|
isKeyboardVisible.current = true;
|
|
119
|
-
|
|
156
|
+
doDodgeKeyboard.current();
|
|
120
157
|
});
|
|
121
158
|
|
|
122
159
|
const hiddenListener = Keyboard.addListener('keyboardDidHide', e => {
|
|
123
160
|
isKeyboardVisible.current = false;
|
|
124
|
-
|
|
161
|
+
doDodgeKeyboard.current();
|
|
125
162
|
});
|
|
126
163
|
|
|
127
164
|
return () => {
|
|
@@ -134,18 +171,90 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
134
171
|
return (
|
|
135
172
|
<ReactHijacker
|
|
136
173
|
doHijack={(node, path) => {
|
|
137
|
-
if (
|
|
174
|
+
if (node?.props?.['dodge_keyboard_scan_off']) return { element: node };
|
|
175
|
+
|
|
176
|
+
const isStandalone = isDodgeInput(node);
|
|
177
|
+
|
|
178
|
+
if (isStandalone || isDodgeScrollable(node, disableTagCheck)) {
|
|
138
179
|
const scrollId = path.join('=>');
|
|
139
180
|
const initNode = () => {
|
|
140
181
|
if (!viewRefsMap.current[scrollId])
|
|
141
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
|
+
}
|
|
142
191
|
}
|
|
143
192
|
const shouldPad = scrollId === paddedId;
|
|
144
193
|
const contentStyle = shouldPad && StyleSheet.flatten(node.props?.contentContainerStyle);
|
|
145
|
-
const
|
|
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
|
+
}
|
|
205
|
+
|
|
206
|
+
const injectChild = (children, childPath) =>
|
|
207
|
+
ReactHijacker({
|
|
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
|
+
}
|
|
222
|
+
|
|
223
|
+
initInputNode();
|
|
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
|
+
}
|
|
245
|
+
|
|
246
|
+
const thatRef = inputNode.props?.ref;
|
|
247
|
+
if (typeof thatRef === 'function') {
|
|
248
|
+
thatRef(r);
|
|
249
|
+
} else if (thatRef) thatRef.current = r;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
146
256
|
|
|
147
257
|
return {
|
|
148
|
-
persist: true,
|
|
149
258
|
props: {
|
|
150
259
|
...node.props,
|
|
151
260
|
...shouldPad ? {
|
|
@@ -159,7 +268,8 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
159
268
|
initNode();
|
|
160
269
|
viewRefsMap.current[scrollId].scrollRef = r;
|
|
161
270
|
} else if (viewRefsMap.current[scrollId]) {
|
|
162
|
-
|
|
271
|
+
viewRefsMap.current[scrollId].scrollRef = undefined;
|
|
272
|
+
doRefCleanup();
|
|
163
273
|
}
|
|
164
274
|
|
|
165
275
|
const thatRef = node.props?.ref;
|
|
@@ -167,65 +277,32 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
167
277
|
thatRef(r);
|
|
168
278
|
} else if (thatRef) thatRef.current = r;
|
|
169
279
|
},
|
|
280
|
+
...isStandalone ? {
|
|
281
|
+
onFocus: (...args) => {
|
|
282
|
+
doDodgeKeyboard.current();
|
|
283
|
+
return node.props?.onFocus?.(...args);
|
|
284
|
+
}
|
|
285
|
+
} : {},
|
|
170
286
|
onLayout: (...args) => {
|
|
171
|
-
|
|
287
|
+
doDodgeKeyboard.current();
|
|
172
288
|
return node.props?.onLayout?.(...args);
|
|
173
289
|
},
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
dodgeFocusIdDuplicateCheck[dodge_focus_id] = true;
|
|
191
|
-
|
|
192
|
-
initNode();
|
|
193
|
-
viewRefsMap.current[scrollId].focusIdMap[inputId] = dodge_focus_id;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return {
|
|
197
|
-
persist: true,
|
|
198
|
-
props: {
|
|
199
|
-
...inputNode.props,
|
|
200
|
-
...dodge_focus_id ? { key: dodge_focus_id } : {},
|
|
201
|
-
onFocus: (...args) => {
|
|
202
|
-
onKeyboardChanged.current();
|
|
203
|
-
return inputNode.props?.onFocus?.(...args);
|
|
204
|
-
},
|
|
205
|
-
onLayout: (...args) => {
|
|
206
|
-
onKeyboardChanged.current();
|
|
207
|
-
return inputNode.props?.onLayout?.(...args);
|
|
208
|
-
},
|
|
209
|
-
ref: r => {
|
|
210
|
-
if (r) {
|
|
211
|
-
initNode();
|
|
212
|
-
|
|
213
|
-
viewRefsMap.current[scrollId].inputRef[inputId] = r;
|
|
214
|
-
} else if (viewRefsMap.current[scrollId]?.inputRef?.[inputId]) {
|
|
215
|
-
delete viewRefsMap.current[scrollId].inputRef[inputId];
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const thatRef = inputNode.props?.ref;
|
|
219
|
-
if (typeof thatRef === 'function') {
|
|
220
|
-
thatRef(r);
|
|
221
|
-
} else if (thatRef) thatRef.current = r;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
}}>
|
|
227
|
-
{node.props?.children}
|
|
228
|
-
</ReactHijacker>
|
|
290
|
+
...isStandalone ? {} :
|
|
291
|
+
hasInternalList ? {
|
|
292
|
+
renderItem: (...args) => {
|
|
293
|
+
const { item, index } = args[0] || {};
|
|
294
|
+
|
|
295
|
+
return injectChild(
|
|
296
|
+
rootRenderItem(...args),
|
|
297
|
+
[
|
|
298
|
+
...path,
|
|
299
|
+
index,
|
|
300
|
+
...typeof rootKeyExtractor === 'function' ?
|
|
301
|
+
[rootKeyExtractor?.(item, index)] : []
|
|
302
|
+
]
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
} : { children: injectChild(node.props?.children, path) }
|
|
229
306
|
}
|
|
230
307
|
};
|
|
231
308
|
}
|
|
@@ -233,28 +310,101 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
233
310
|
{children}
|
|
234
311
|
</ReactHijacker>
|
|
235
312
|
);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const niceFunction = (func, message) => {
|
|
316
|
+
return (...args) => {
|
|
317
|
+
try {
|
|
318
|
+
return func(...args);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error(`${message} err:`, error);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
236
323
|
}
|
|
237
324
|
|
|
238
325
|
const isNumber = t => typeof t === 'number' && !isNaN(t) && Number.isFinite(t);
|
|
239
326
|
|
|
327
|
+
const REACT_SYMBOLS = {
|
|
328
|
+
forwardRef: Symbol.for('react.forward_ref'),
|
|
329
|
+
memo: Symbol.for('react.memo')
|
|
330
|
+
};
|
|
331
|
+
|
|
240
332
|
export function ReactHijacker({ children, doHijack, path }) {
|
|
241
|
-
|
|
333
|
+
const renderRefs = useMemo(() => new Map(), []);
|
|
242
334
|
|
|
243
|
-
const
|
|
244
|
-
|
|
335
|
+
const instantDoHijack = useRef();
|
|
336
|
+
instantDoHijack.current = doHijack;
|
|
337
|
+
|
|
338
|
+
const injectIntoTree = (node, path = [], arrayIndex) => {
|
|
339
|
+
if (!node) return node;
|
|
245
340
|
if (Array.isArray(node)) {
|
|
246
|
-
|
|
341
|
+
path = [...path, ...arrayIndex === undefined ? [0] : [arrayIndex]];
|
|
342
|
+
return Children.map(node, (v, i) => injectIntoTree(v, path, i));
|
|
247
343
|
}
|
|
248
|
-
if (!
|
|
344
|
+
if (!isValidElement(node)) return node;
|
|
249
345
|
|
|
250
|
-
path = [...path, ...
|
|
346
|
+
path = [...path, ...arrayIndex === undefined ? [0] : [arrayIndex], getNodeId(node)];
|
|
251
347
|
|
|
252
348
|
let thisObj;
|
|
253
|
-
if (thisObj =
|
|
254
|
-
const {
|
|
255
|
-
proceedHijacking = persist;
|
|
349
|
+
if (thisObj = instantDoHijack.current?.(node, path)) {
|
|
350
|
+
const { element, props } = thisObj;
|
|
256
351
|
|
|
257
|
-
|
|
352
|
+
if (Object.hasOwn(thisObj, 'element')) return element;
|
|
353
|
+
if (props) return cloneElement(node, props);
|
|
354
|
+
return node;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (!isHostElement(node)) {
|
|
358
|
+
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];
|
|
364
|
+
|
|
365
|
+
// if (doLogging) console.log('wrapNodeType path:', pathway, ' node:', nodeType);
|
|
366
|
+
const render = (renderedNode) => {
|
|
367
|
+
// if (doLogging) console.log('deep path:', pathway, ' node:', renderedNode);
|
|
368
|
+
return injectIntoTree(renderedNode, pathway);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let newType;
|
|
372
|
+
|
|
373
|
+
if (typeof nodeType === 'function') { // check self closed tag
|
|
374
|
+
newType = hijackRender(nodeType, render);
|
|
375
|
+
} else if (nodeType?.$$typeof === REACT_SYMBOLS.forwardRef) {
|
|
376
|
+
newType = forwardRef(hijackRender(nodeType.render, render));
|
|
377
|
+
} else if (nodeType?.$$typeof === REACT_SYMBOLS.memo) {
|
|
378
|
+
newType = memo(wrapNodeType(nodeType.type, pathway), nodeType.compare);
|
|
379
|
+
newType.displayName = nodeType.displayName || nodeType.name;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (newType) {
|
|
383
|
+
if (!renderRefStore) renderRefs.set(nodeType, renderRefStore = {});
|
|
384
|
+
renderRefStore[path_id] = newType;
|
|
385
|
+
return newType;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return nodeType;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (
|
|
392
|
+
typeof node.type === 'function' || // check self closed tag
|
|
393
|
+
node.type?.$$typeof === REACT_SYMBOLS.forwardRef || // check forwardRef
|
|
394
|
+
node.type?.$$typeof === REACT_SYMBOLS.memo // check memo
|
|
395
|
+
) {
|
|
396
|
+
// if (doLogging) console.log('doLog path:', path, ' node:', node);
|
|
397
|
+
const injectedType = wrapNodeType(node.type, path.slice(0, -1), node.key);
|
|
398
|
+
return createElement(
|
|
399
|
+
injectedType,
|
|
400
|
+
{
|
|
401
|
+
...node.props,
|
|
402
|
+
key: node.key,
|
|
403
|
+
// ...isForwardRef ? { ref: node.ref } : {}
|
|
404
|
+
},
|
|
405
|
+
node.props?.children
|
|
406
|
+
);
|
|
407
|
+
}
|
|
258
408
|
}
|
|
259
409
|
|
|
260
410
|
const children = node.props?.children;
|
|
@@ -267,40 +417,79 @@ export function ReactHijacker({ children, doHijack, path }) {
|
|
|
267
417
|
};
|
|
268
418
|
|
|
269
419
|
return injectIntoTree(children, path);
|
|
270
|
-
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const hijackRender = (type, doHijack) =>
|
|
423
|
+
new Proxy(type, {
|
|
424
|
+
apply(target, thisArg, args) {
|
|
425
|
+
const renderedNode = Reflect.apply(target, thisArg, args);
|
|
426
|
+
return doHijack(renderedNode);
|
|
427
|
+
},
|
|
428
|
+
get(target, prop) {
|
|
429
|
+
return target[prop];
|
|
430
|
+
},
|
|
431
|
+
set(target, prop, value) {
|
|
432
|
+
target[prop] = value;
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
function getNodeId(node, typeObj, typeKey) {
|
|
438
|
+
if ((!node && !typeObj) || (node && !isValidElement(node))) return `${node}`;
|
|
439
|
+
|
|
440
|
+
const type = typeObj || node?.type;
|
|
441
|
+
const withKey = (s) => `${s}:${[typeKey || node?.key].find(v => ![null, undefined].includes(v)) || null}`;
|
|
442
|
+
const withWrapper = (tag, name) => `@${tag}#${name}#`;
|
|
443
|
+
|
|
444
|
+
// Host component
|
|
445
|
+
if (typeof type === 'string' || typeof type === 'number') return withKey(type);
|
|
271
446
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return
|
|
447
|
+
if (type?.displayName) return withKey(type.displayName);
|
|
448
|
+
|
|
449
|
+
// Function component
|
|
450
|
+
if (typeof type === "function") return withKey(type.name);
|
|
451
|
+
|
|
452
|
+
if (type?.$$typeof === REACT_SYMBOLS.forwardRef) { // forwardRef
|
|
453
|
+
return withKey(withWrapper('forwardRef', type.render?.name));
|
|
454
|
+
} else if (type?.$$typeof === REACT_SYMBOLS.memo) { // memo
|
|
455
|
+
return withKey(withWrapper('memo', ''));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return withKey(
|
|
459
|
+
type?.name ||
|
|
460
|
+
// node?.name ||
|
|
461
|
+
withWrapper('Fragment', type?.$$typeof?.toString?.() || '')
|
|
462
|
+
);
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
export function isHostElement(node) {
|
|
466
|
+
if (!node) return true;
|
|
467
|
+
const t = typeof node.type;
|
|
468
|
+
|
|
469
|
+
return (
|
|
470
|
+
t === 'string' ||
|
|
471
|
+
t === 'number' || // RN internal tags
|
|
472
|
+
node?.type?.Context !== undefined ||
|
|
473
|
+
(t !== 'function' && t !== 'object')
|
|
474
|
+
);
|
|
276
475
|
}
|
|
277
476
|
|
|
278
|
-
const isDodgeScrollable = (element, disableTagCheck) => {
|
|
279
|
-
if (element?.props?.['
|
|
280
|
-
return true;
|
|
477
|
+
export const isDodgeScrollable = (element, disableTagCheck) => {
|
|
478
|
+
if (element?.props?.['dodge_keyboard_scrollable']) return true;
|
|
281
479
|
if (!element?.type || element?.props?.horizontal || disableTagCheck) return false;
|
|
282
480
|
|
|
283
481
|
const scrollableTypes = ["ScrollView", "FlatList", "SectionList", "VirtualizedList"];
|
|
284
482
|
|
|
285
|
-
return scrollableTypes.includes(element.type
|
|
483
|
+
return scrollableTypes.includes(element.type?.displayName)
|
|
286
484
|
|| scrollableTypes.includes(element.type?.name);
|
|
287
485
|
};
|
|
288
486
|
|
|
289
|
-
const isDodgeInput = (element, disableTagCheck) => {
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
element?.props?.['dodge-keyboard-focus-id']
|
|
293
|
-
) return true;
|
|
294
|
-
if (disableTagCheck) return false;
|
|
295
|
-
const { placeholder, onChangeText } = element?.props || {};
|
|
296
|
-
if (
|
|
297
|
-
typeof onChangeText === 'function' ||
|
|
298
|
-
(typeof placeholder === 'string' && placeholder.trim())
|
|
299
|
-
) return true;
|
|
300
|
-
if (!element?.type) return false;
|
|
487
|
+
export const isDodgeInput = (element, disableTagCheck) => {
|
|
488
|
+
if (element?.props?.['dodge_keyboard_input']) return true;
|
|
489
|
+
if (disableTagCheck || !element?.type) return false;
|
|
301
490
|
|
|
302
491
|
const inputTypes = ["TextInput"];
|
|
303
492
|
|
|
304
|
-
return inputTypes.includes(element.type
|
|
493
|
+
return inputTypes.includes(element.type?.displayName)
|
|
305
494
|
|| inputTypes.includes(element.type?.name);
|
|
306
495
|
};
|
package/package.json
CHANGED