react-native-dodge-keyboard 1.0.0 → 1.0.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/index.d.ts +22 -15
- package/index.js +342 -164
- package/package.json +1 -1
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,81 @@ 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 injectChild = (children, childPath) =>
|
|
199
|
+
ReactHijacker({
|
|
200
|
+
children,
|
|
201
|
+
path: childPath,
|
|
202
|
+
doHijack: (inputNode, path) => {
|
|
203
|
+
if (isDodgeInput(inputNode, disableTagCheck)) {
|
|
204
|
+
const inputId = path.join('=>');
|
|
205
|
+
const initInputNode = () => {
|
|
206
|
+
initNode();
|
|
207
|
+
if (!viewRefsMap.current[scrollId].inputRef[inputId])
|
|
208
|
+
viewRefsMap.current[scrollId].inputRef[inputId] = {};
|
|
209
|
+
viewRefsMap.current[scrollId].inputRef[inputId].props = {
|
|
210
|
+
dodge_keyboard_offset: inputNode.props?.dodge_keyboard_offset,
|
|
211
|
+
dodge_keyboard_lift: inputNode.props?.dodge_keyboard_lift
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
initInputNode();
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
props: {
|
|
219
|
+
...inputNode.props,
|
|
220
|
+
onFocus: (...args) => {
|
|
221
|
+
doDodgeKeyboard.current();
|
|
222
|
+
return inputNode.props?.onFocus?.(...args);
|
|
223
|
+
},
|
|
224
|
+
onLayout: (...args) => {
|
|
225
|
+
doDodgeKeyboard.current();
|
|
226
|
+
return inputNode.props?.onLayout?.(...args);
|
|
227
|
+
},
|
|
228
|
+
ref: r => {
|
|
229
|
+
if (r) {
|
|
230
|
+
initInputNode();
|
|
231
|
+
|
|
232
|
+
viewRefsMap.current[scrollId].inputRef[inputId].ref = r;
|
|
233
|
+
} else if (viewRefsMap.current[scrollId]?.inputRef?.[inputId]) {
|
|
234
|
+
delete viewRefsMap.current[scrollId].inputRef[inputId];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const thatRef = inputNode.props?.ref;
|
|
238
|
+
if (typeof thatRef === 'function') {
|
|
239
|
+
thatRef(r);
|
|
240
|
+
} else if (thatRef) thatRef.current = r;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
});
|
|
146
247
|
|
|
147
248
|
return {
|
|
148
|
-
persist: true,
|
|
149
249
|
props: {
|
|
150
250
|
...node.props,
|
|
151
251
|
...shouldPad ? {
|
|
@@ -167,65 +267,32 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
167
267
|
thatRef(r);
|
|
168
268
|
} else if (thatRef) thatRef.current = r;
|
|
169
269
|
},
|
|
270
|
+
...isStandalone ? {
|
|
271
|
+
onFocus: (...args) => {
|
|
272
|
+
doDodgeKeyboard.current();
|
|
273
|
+
return node.props?.onFocus?.(...args);
|
|
274
|
+
}
|
|
275
|
+
} : {},
|
|
170
276
|
onLayout: (...args) => {
|
|
171
|
-
|
|
277
|
+
doDodgeKeyboard.current();
|
|
172
278
|
return node.props?.onLayout?.(...args);
|
|
173
279
|
},
|
|
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>
|
|
280
|
+
...isStandalone ? {} :
|
|
281
|
+
hasInternalList ? {
|
|
282
|
+
renderItem: (...args) => {
|
|
283
|
+
const { item, index } = args[0] || {};
|
|
284
|
+
|
|
285
|
+
return injectChild(
|
|
286
|
+
rootRenderItem(...args),
|
|
287
|
+
[
|
|
288
|
+
...path,
|
|
289
|
+
index,
|
|
290
|
+
...typeof rootKeyExtractor === 'function' ?
|
|
291
|
+
[rootKeyExtractor?.(item, index)] : []
|
|
292
|
+
]
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
} : { children: injectChild(node.props?.children, path) }
|
|
229
296
|
}
|
|
230
297
|
};
|
|
231
298
|
}
|
|
@@ -233,28 +300,100 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
233
300
|
{children}
|
|
234
301
|
</ReactHijacker>
|
|
235
302
|
);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const niceFunction = (func, message) => {
|
|
306
|
+
return (...args) => {
|
|
307
|
+
try {
|
|
308
|
+
return func(...args);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error(`${message} err:`, error);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
236
313
|
}
|
|
237
314
|
|
|
238
315
|
const isNumber = t => typeof t === 'number' && !isNaN(t) && Number.isFinite(t);
|
|
239
316
|
|
|
317
|
+
const REACT_SYMBOLS = {
|
|
318
|
+
forwardRef: Symbol.for('react.forward_ref'),
|
|
319
|
+
memo: Symbol.for('react.memo')
|
|
320
|
+
};
|
|
321
|
+
|
|
240
322
|
export function ReactHijacker({ children, doHijack, path }) {
|
|
241
|
-
|
|
323
|
+
const renderRefs = useMemo(() => new Map(), []);
|
|
324
|
+
|
|
325
|
+
const instantDoHijack = useRef();
|
|
326
|
+
instantDoHijack.current = doHijack;
|
|
242
327
|
|
|
243
328
|
const injectIntoTree = (node, path = [], wasArray) => {
|
|
244
|
-
if (!
|
|
329
|
+
if (!node) return node;
|
|
245
330
|
if (Array.isArray(node)) {
|
|
246
|
-
return
|
|
331
|
+
return Children.map(node, (v, i) => injectIntoTree(v, [...path, i], true));
|
|
247
332
|
}
|
|
248
|
-
if (!
|
|
333
|
+
if (!isValidElement(node)) return node;
|
|
249
334
|
|
|
250
|
-
path = [...path, ...wasArray ? [] : [0],
|
|
335
|
+
path = [...path, ...wasArray ? [] : [0], getNodeId(node)];
|
|
251
336
|
|
|
252
337
|
let thisObj;
|
|
253
|
-
if (thisObj =
|
|
254
|
-
const {
|
|
255
|
-
|
|
338
|
+
if (thisObj = instantDoHijack.current?.(node, path)) {
|
|
339
|
+
const { element, props } = thisObj;
|
|
340
|
+
|
|
341
|
+
if (Object.hasOwn(thisObj, 'element')) return element;
|
|
342
|
+
if (props) return cloneElement(node, props);
|
|
343
|
+
return node;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (!isHostElement(node)) {
|
|
347
|
+
const wrapNodeType = (nodeType, pathway, pathKey) => {
|
|
348
|
+
pathway = [...pathway, getNodeId(undefined, nodeType, pathKey)];
|
|
349
|
+
const path_id = pathway.join(',');
|
|
350
|
+
let renderRefStore = renderRefs.get(nodeType);
|
|
351
|
+
|
|
352
|
+
if (renderRefStore?.[path_id]) return renderRefStore[path_id];
|
|
353
|
+
|
|
354
|
+
// if (doLogging) console.log('wrapNodeType path:', pathway, ' node:', nodeType);
|
|
355
|
+
const render = (renderedNode) => {
|
|
356
|
+
// if (doLogging) console.log('deep path:', pathway, ' node:', renderedNode);
|
|
357
|
+
return injectIntoTree(renderedNode, pathway);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
let newType;
|
|
361
|
+
|
|
362
|
+
if (typeof nodeType === 'function') { // check self closed tag
|
|
363
|
+
newType = hijackRender(nodeType, render);
|
|
364
|
+
} else if (nodeType?.$$typeof === REACT_SYMBOLS.forwardRef) {
|
|
365
|
+
newType = forwardRef(hijackRender(nodeType.render, render));
|
|
366
|
+
} else if (nodeType?.$$typeof === REACT_SYMBOLS.memo) {
|
|
367
|
+
newType = memo(wrapNodeType(nodeType.type, pathway), nodeType.compare);
|
|
368
|
+
newType.displayName = nodeType.displayName || nodeType.name;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (newType) {
|
|
372
|
+
if (!renderRefStore) renderRefs.set(nodeType, renderRefStore = {});
|
|
373
|
+
renderRefStore[path_id] = newType;
|
|
374
|
+
return newType;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return nodeType;
|
|
378
|
+
}
|
|
256
379
|
|
|
257
|
-
|
|
380
|
+
if (
|
|
381
|
+
typeof node.type === 'function' || // check self closed tag
|
|
382
|
+
node.type?.$$typeof === REACT_SYMBOLS.forwardRef || // check forwardRef
|
|
383
|
+
node.type?.$$typeof === REACT_SYMBOLS.memo // check memo
|
|
384
|
+
) {
|
|
385
|
+
// if (doLogging) console.log('doLog path:', path, ' node:', node);
|
|
386
|
+
const injectedType = wrapNodeType(node.type, path.slice(0, -1), node.key);
|
|
387
|
+
return createElement(
|
|
388
|
+
injectedType,
|
|
389
|
+
{
|
|
390
|
+
...node.props,
|
|
391
|
+
key: node.key,
|
|
392
|
+
// ...isForwardRef ? { ref: node.ref } : {}
|
|
393
|
+
},
|
|
394
|
+
node.props?.children
|
|
395
|
+
);
|
|
396
|
+
}
|
|
258
397
|
}
|
|
259
398
|
|
|
260
399
|
const children = node.props?.children;
|
|
@@ -267,40 +406,79 @@ export function ReactHijacker({ children, doHijack, path }) {
|
|
|
267
406
|
};
|
|
268
407
|
|
|
269
408
|
return injectIntoTree(children, path);
|
|
270
|
-
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const hijackRender = (type, doHijack) =>
|
|
412
|
+
new Proxy(type, {
|
|
413
|
+
apply(target, thisArg, args) {
|
|
414
|
+
const renderedNode = Reflect.apply(target, thisArg, args);
|
|
415
|
+
return doHijack(renderedNode);
|
|
416
|
+
},
|
|
417
|
+
get(target, prop) {
|
|
418
|
+
return target[prop];
|
|
419
|
+
},
|
|
420
|
+
set(target, prop, value) {
|
|
421
|
+
target[prop] = value;
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
function getNodeId(node, typeObj, typeKey) {
|
|
427
|
+
if ((!node && !typeObj) || (node && !isValidElement(node))) return `${node}`;
|
|
428
|
+
|
|
429
|
+
const type = typeObj || node?.type;
|
|
430
|
+
const withKey = (s) => `${s}:${[typeKey || node?.key].find(v => ![null, undefined].includes(v)) || null}`;
|
|
431
|
+
const withWrapper = (tag, name) => `@${tag}#${name}#`;
|
|
432
|
+
|
|
433
|
+
// Host component
|
|
434
|
+
if (typeof type === 'string' || typeof type === 'number') return withKey(type);
|
|
271
435
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return
|
|
436
|
+
if (type?.displayName) return withKey(type.displayName);
|
|
437
|
+
|
|
438
|
+
// Function component
|
|
439
|
+
if (typeof type === "function") return withKey(type.name);
|
|
440
|
+
|
|
441
|
+
if (type?.$$typeof === REACT_SYMBOLS.forwardRef) { // forwardRef
|
|
442
|
+
return withKey(withWrapper('forwardRef', type.render?.name));
|
|
443
|
+
} else if (type?.$$typeof === REACT_SYMBOLS.memo) { // memo
|
|
444
|
+
return withKey(withWrapper('memo', ''));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return withKey(
|
|
448
|
+
type?.name ||
|
|
449
|
+
// node?.name ||
|
|
450
|
+
withWrapper('Fragment', type?.$$typeof?.toString?.() || '')
|
|
451
|
+
);
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
export function isHostElement(node) {
|
|
455
|
+
if (!node) return true;
|
|
456
|
+
const t = typeof node.type;
|
|
457
|
+
|
|
458
|
+
return (
|
|
459
|
+
t === 'string' ||
|
|
460
|
+
t === 'number' || // RN internal tags
|
|
461
|
+
node?.type?.Context !== undefined ||
|
|
462
|
+
(t !== 'function' && t !== 'object')
|
|
463
|
+
);
|
|
276
464
|
}
|
|
277
465
|
|
|
278
|
-
const isDodgeScrollable = (element, disableTagCheck) => {
|
|
279
|
-
if (element?.props?.['
|
|
280
|
-
return true;
|
|
466
|
+
export const isDodgeScrollable = (element, disableTagCheck) => {
|
|
467
|
+
if (element?.props?.['dodge_keyboard_scrollable']) return true;
|
|
281
468
|
if (!element?.type || element?.props?.horizontal || disableTagCheck) return false;
|
|
282
469
|
|
|
283
470
|
const scrollableTypes = ["ScrollView", "FlatList", "SectionList", "VirtualizedList"];
|
|
284
471
|
|
|
285
|
-
return scrollableTypes.includes(element.type
|
|
472
|
+
return scrollableTypes.includes(element.type?.displayName)
|
|
286
473
|
|| scrollableTypes.includes(element.type?.name);
|
|
287
474
|
};
|
|
288
475
|
|
|
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;
|
|
476
|
+
export const isDodgeInput = (element, disableTagCheck) => {
|
|
477
|
+
if (element?.props?.['dodge_keyboard_input']) return true;
|
|
478
|
+
if (disableTagCheck || !element?.type) return false;
|
|
301
479
|
|
|
302
480
|
const inputTypes = ["TextInput"];
|
|
303
481
|
|
|
304
|
-
return inputTypes.includes(element.type
|
|
482
|
+
return inputTypes.includes(element.type?.displayName)
|
|
305
483
|
|| inputTypes.includes(element.type?.name);
|
|
306
484
|
};
|
package/package.json
CHANGED