react-native-dodge-keyboard 1.0.3 → 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 +5 -2
- package/index.js +199 -117
- 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,7 +100,7 @@ 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
|
|
package/index.js
CHANGED
|
@@ -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')
|
|
@@ -27,11 +27,17 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
27
27
|
const doDodgeKeyboard = useRef();
|
|
28
28
|
const previousLift = useRef();
|
|
29
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
|
}
|
|
@@ -39,8 +45,11 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
39
45
|
/**
|
|
40
46
|
* @param {import('react-native').KeyboardEvent | undefined} event
|
|
41
47
|
* @param {boolean} visible
|
|
48
|
+
* @param {boolean} fromIdle
|
|
42
49
|
*/
|
|
43
|
-
doDodgeKeyboard.current = (event, visible) => {
|
|
50
|
+
doDodgeKeyboard.current = (event, visible, fromIdle) => {
|
|
51
|
+
if (Platform.OS === 'ios' && event && !event?.isEventFromThisApp) return;
|
|
52
|
+
|
|
44
53
|
if (typeof visible !== 'boolean') {
|
|
45
54
|
if (typeof wasVisible.current === 'boolean') {
|
|
46
55
|
visible = wasVisible.current;
|
|
@@ -48,79 +57,123 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
wasVisible.current = visible;
|
|
60
|
+
if (event) lastKeyboardEvent.current = event;
|
|
51
61
|
|
|
52
62
|
try {
|
|
53
63
|
const keyboardInfo = event?.endCoordinates || Keyboard.metrics();
|
|
54
|
-
const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
|
|
55
64
|
|
|
56
65
|
// console.log('doDodgeKeyboard');
|
|
66
|
+
|
|
67
|
+
if (pendingIdleTask.current !== undefined)
|
|
68
|
+
cancelIdleCallback(pendingIdleTask.current);
|
|
69
|
+
pendingIdleTask.current = undefined;
|
|
70
|
+
|
|
57
71
|
if (
|
|
58
72
|
visible &&
|
|
59
73
|
keyboardInfo &&
|
|
60
74
|
!disabled &&
|
|
61
|
-
(keyboardInfo
|
|
62
|
-
keyboardInfo.height + keyboardInfo.screenY === windowHeight) &&
|
|
75
|
+
(!isOffScreenY(keyboardInfo) && !isOffScreenX(keyboardInfo)) &&
|
|
63
76
|
keyboardInfo.screenY
|
|
64
77
|
) {
|
|
65
78
|
// console.log('doDodgeKeyboard 1 entries:', Object.keys(viewRefsMap.current).length);
|
|
66
|
-
|
|
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) {
|
|
67
96
|
const { scrollRef, inputRef, __is_standalone, _standalone_props } = obj;
|
|
68
97
|
if (scrollRef) {
|
|
69
98
|
if (__is_standalone) {
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
});
|
|
83
116
|
return;
|
|
84
117
|
}
|
|
85
118
|
} else {
|
|
86
119
|
for (const { ref: inputObj, props } of Object.values(inputRef)) {
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
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) => {
|
|
90
133
|
inputObj.measureLayout(scrollRef, (l, t, w, h) => { // t is fixed
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
tryPerformScroll(scrollRef, newScrollY, true);
|
|
117
|
-
setCurrentPaddedScroller();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
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
|
+
});
|
|
120
159
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
});
|
|
124
177
|
return;
|
|
125
178
|
}
|
|
126
179
|
}
|
|
@@ -183,7 +236,7 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
183
236
|
const nodeIdIte = useRef(0);
|
|
184
237
|
|
|
185
238
|
const onHijackNode = node => {
|
|
186
|
-
if (node
|
|
239
|
+
if (offDodgeScan(node)) return createHijackedElement(node);
|
|
187
240
|
|
|
188
241
|
const isStandalone = isDodgeInput(node);
|
|
189
242
|
if (!isStandalone && !isDodgeScrollable(node, disableTagCheck)) return;
|
|
@@ -216,67 +269,62 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
216
269
|
delete viewRefsMap.current[scrollId];
|
|
217
270
|
}
|
|
218
271
|
|
|
219
|
-
const injectChild =
|
|
220
|
-
|
|
221
|
-
children,
|
|
222
|
-
path: childPath,
|
|
223
|
-
doHijack: inputNode => {
|
|
224
|
-
if (node?.props?.__dodging_keyboard) return;
|
|
225
|
-
|
|
226
|
-
if (!isDodgeInput(inputNode, disableTagCheck)) return;
|
|
227
|
-
|
|
228
|
-
const inputRenderer = () => {
|
|
229
|
-
const inputId = useMemo(() => `${++nodeIdIte.current}`, []);
|
|
230
|
-
const initInputNode = () => {
|
|
231
|
-
initNode();
|
|
232
|
-
if (!viewRefsMap.current[scrollId].inputRef[inputId])
|
|
233
|
-
viewRefsMap.current[scrollId].inputRef[inputId] = {};
|
|
234
|
-
viewRefsMap.current[scrollId].inputRef[inputId].props = {
|
|
235
|
-
dodge_keyboard_offset: inputNode.props?.dodge_keyboard_offset,
|
|
236
|
-
dodge_keyboard_lift: inputNode.props?.dodge_keyboard_lift
|
|
237
|
-
};
|
|
238
|
-
}
|
|
272
|
+
const injectChild = inputNode => {
|
|
273
|
+
if (offDodgeScan(inputNode)) return createHijackedElement(inputNode);
|
|
239
274
|
|
|
240
|
-
|
|
275
|
+
if (!isDodgeInput(inputNode, disableTagCheck)) return;
|
|
241
276
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
ref: r => {
|
|
254
|
-
if (r) {
|
|
255
|
-
initInputNode();
|
|
256
|
-
|
|
257
|
-
viewRefsMap.current[scrollId].inputRef[inputId].ref = r;
|
|
258
|
-
} else if (viewRefsMap.current[scrollId]?.inputRef?.[inputId]) {
|
|
259
|
-
delete viewRefsMap.current[scrollId].inputRef[inputId];
|
|
260
|
-
doRefCleanup();
|
|
261
|
-
}
|
|
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
|
+
}
|
|
262
288
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
+
}
|
|
269
311
|
|
|
270
|
-
|
|
312
|
+
const thatRef = inputNode.props?.ref;
|
|
313
|
+
if (typeof thatRef === 'function') {
|
|
314
|
+
thatRef(r);
|
|
315
|
+
} else if (thatRef) thatRef.current = r;
|
|
271
316
|
}
|
|
317
|
+
};
|
|
272
318
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
319
|
+
return cloneElement(inputNode, newProps);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return createHijackedElement(
|
|
323
|
+
<__HijackNode>
|
|
324
|
+
{inputRenderer}
|
|
325
|
+
</__HijackNode>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
280
328
|
|
|
281
329
|
const newProps = {
|
|
282
330
|
...node.props,
|
|
@@ -314,9 +362,20 @@ export default function ({ children, offset = 10, disabled, onHandleDodging, dis
|
|
|
314
362
|
...isStandalone ? {} :
|
|
315
363
|
hasInternalList ? {
|
|
316
364
|
renderItem: (...args) => {
|
|
317
|
-
return
|
|
365
|
+
return (
|
|
366
|
+
<ReactHijacker
|
|
367
|
+
doHijack={injectChild}>
|
|
368
|
+
{rootRenderItem(...args)}
|
|
369
|
+
</ReactHijacker>
|
|
370
|
+
);
|
|
318
371
|
}
|
|
319
|
-
} : {
|
|
372
|
+
} : {
|
|
373
|
+
children:
|
|
374
|
+
ReactHijacker({
|
|
375
|
+
children: node.props?.children,
|
|
376
|
+
doHijack: injectChild
|
|
377
|
+
})
|
|
378
|
+
}
|
|
320
379
|
};
|
|
321
380
|
|
|
322
381
|
return cloneElement(node, newProps);
|
|
@@ -498,6 +557,8 @@ export function isHostElement(node) {
|
|
|
498
557
|
);
|
|
499
558
|
}
|
|
500
559
|
|
|
560
|
+
const offDodgeScan = (node) => node?.props?.dodge_keyboard_scan_off || node?.props?.__dodging_keyboard;
|
|
561
|
+
|
|
501
562
|
export const isDodgeScrollable = (element, disableTagCheck) => {
|
|
502
563
|
if (element?.props?.['dodge_keyboard_scrollable']) return true;
|
|
503
564
|
if (!element?.type || element?.props?.horizontal || disableTagCheck) return false;
|
|
@@ -518,6 +579,19 @@ export const isDodgeInput = (element, disableTagCheck) => {
|
|
|
518
579
|
|| inputTypes.includes(element.type?.name);
|
|
519
580
|
};
|
|
520
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
|
+
|
|
521
595
|
export const KeyboardPlaceholderView = ({ doHeight }) => {
|
|
522
596
|
const height = useAnimatedValue(0);
|
|
523
597
|
|
|
@@ -542,19 +616,27 @@ export const KeyboardPlaceholderView = ({ doHeight }) => {
|
|
|
542
616
|
const { endCoordinates, isEventFromThisApp, duration } = event;
|
|
543
617
|
if (Platform.OS === 'ios' && !isEventFromThisApp) return;
|
|
544
618
|
|
|
545
|
-
const kh = visible
|
|
619
|
+
const kh = (visible && !isOffScreenX(endCoordinates) && !isOffScreenY(endCoordinates, .3))
|
|
620
|
+
? Math.max(Dimensions.get('window').height - endCoordinates.screenY, 0)
|
|
621
|
+
: 0;
|
|
622
|
+
|
|
546
623
|
const newHeight = Math.max(0, instantDoHeight.current ? instantDoHeight.current(kh) : kh);
|
|
547
|
-
const newDuration = (Math.abs(height._value - newHeight) * duration) / endCoordinates.height;
|
|
624
|
+
const newDuration = (Math.abs(height._value - newHeight) * duration) / Math.max(0, endCoordinates.height);
|
|
548
625
|
|
|
549
626
|
Animated.timing(height, {
|
|
550
|
-
duration: newDuration,
|
|
627
|
+
duration: newDuration || 0,
|
|
551
628
|
toValue: newHeight,
|
|
552
629
|
useNativeDriver: false
|
|
553
630
|
}).start();
|
|
554
631
|
}
|
|
555
632
|
|
|
556
633
|
const initialMetric = Keyboard.metrics();
|
|
557
|
-
if (initialMetric)
|
|
634
|
+
if (initialMetric)
|
|
635
|
+
updateKeyboardHeight({
|
|
636
|
+
endCoordinates: initialMetric,
|
|
637
|
+
isEventFromThisApp: true,
|
|
638
|
+
duration: 0
|
|
639
|
+
}, true);
|
|
558
640
|
|
|
559
641
|
const frameListener = Keyboard.addListener('keyboardDidChangeFrame', e => updateKeyboardHeight(e));
|
|
560
642
|
const showListener = Keyboard.addListener(
|
package/package.json
CHANGED
package/TODO
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
keyboard not dodging in state changes
|