Skip to content

Commit 05f35c2

Browse files
nossbiggfacebook-github-bot
authored andcommitted
Expose isLocalUserInfoKey to keyboard event notifications (#23245)
Summary: Given two apps loaded side-by-side and when a `Keyboard` event is triggered, there is no way to ascertain which app triggered the keyboard event. This ambiguity can arise in slide over/split view scenarios. This pull request exposes the `isLocalUserInfoKey` property of the native `UIKeyboard` iOS events to the `Keyboard` event listener; this property will return `true` for the app that triggered the keyboard event. (Also, I threw in a couple of Keyboard.js tests just for fun 😅) [iOS][Added] - Expose isLocalUserInfoKey to keyboard event notifications 1. Load two apps side-by-side, with the app on the left side subscribing to the keyboard events (and logging out the events as they happen) 1. Trigger a keyboard to appear with the left app. The logged keyboard event will contain the `isEventFromThisApp` property which will be true. 1. Dismiss the keyboard 1. Trigger a keyboard to appear with the right app. The left app will still log the keyboard event, but the event's `isEventFromThisApp` property will be false (because the left app didn't trigger the keyboard event) Pull Request resolved: #23245 Differential Revision: D13928612 Pulled By: hramos fbshipit-source-id: 6d74d2565e2af62328485fd9da86f15f9e2ccfab
1 parent dda2b82 commit 05f35c2

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

Libraries/Components/Keyboard/Keyboard.js

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export type KeyboardEvent = $ReadOnly<{|
3737
easing?: string,
3838
endCoordinates: ScreenRect,
3939
startCoordinates?: ScreenRect,
40+
isEventFromThisApp?: boolean,
4041
|}>;
4142

4243
type KeyboardEventListener = (e: KeyboardEvent) => void;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @format
8+
* @flow
9+
* @emails oncall+react_native
10+
*/
11+
12+
'use strict';
13+
14+
const Keyboard = require('Keyboard');
15+
const dismissKeyboard = require('dismissKeyboard');
16+
const LayoutAnimation = require('LayoutAnimation');
17+
18+
const NativeEventEmitter = require('NativeEventEmitter');
19+
const NativeModules = require('NativeModules');
20+
21+
jest.mock('LayoutAnimation');
22+
23+
describe('Keyboard', () => {
24+
beforeEach(() => {
25+
jest.resetAllMocks();
26+
});
27+
28+
it('exposes KeyboardEventEmitter methods', () => {
29+
const KeyboardObserver = NativeModules.KeyboardObserver;
30+
const KeyboardEventEmitter = new NativeEventEmitter(KeyboardObserver);
31+
32+
// $FlowFixMe
33+
expect(Keyboard._subscriber).toBe(KeyboardEventEmitter._subscriber);
34+
expect(Keyboard._nativeModule).toBe(KeyboardEventEmitter._nativeModule);
35+
});
36+
37+
it('uses dismissKeyboard utility', () => {
38+
expect(Keyboard.dismiss).toBe(dismissKeyboard);
39+
});
40+
41+
describe('scheduling layout animation', () => {
42+
const scheduleLayoutAnimation = (
43+
duration: number | null,
44+
easing: string | null,
45+
): void => Keyboard.scheduleLayoutAnimation({duration, easing});
46+
47+
it('triggers layout animation', () => {
48+
scheduleLayoutAnimation(12, 'spring');
49+
expect(LayoutAnimation.configureNext).toHaveBeenCalledWith({
50+
duration: 12,
51+
update: {
52+
duration: 12,
53+
type: 'spring',
54+
},
55+
});
56+
});
57+
58+
it('does not trigger animation when duration is null', () => {
59+
scheduleLayoutAnimation(null, 'spring');
60+
expect(LayoutAnimation.configureNext).not.toHaveBeenCalled();
61+
});
62+
63+
it('does not trigger animation when duration is 0', () => {
64+
scheduleLayoutAnimation(0, 'spring');
65+
expect(LayoutAnimation.configureNext).not.toHaveBeenCalled();
66+
});
67+
68+
describe('animation update type', () => {
69+
const assertAnimationUpdateType = type =>
70+
expect(LayoutAnimation.configureNext).toHaveBeenCalledWith(
71+
expect.objectContaining({
72+
duration: expect.anything(),
73+
update: {duration: expect.anything(), type},
74+
}),
75+
);
76+
77+
it('retrieves type from LayoutAnimation', () => {
78+
scheduleLayoutAnimation(12, 'linear');
79+
assertAnimationUpdateType('linear');
80+
});
81+
82+
it("defaults to 'keyboard' when key in LayoutAnimation is not found", () => {
83+
scheduleLayoutAnimation(12, 'some-unknown-animation-type');
84+
assertAnimationUpdateType('keyboard');
85+
});
86+
87+
it("defaults to 'keyboard' when easing is null", () => {
88+
scheduleLayoutAnimation(12, null);
89+
assertAnimationUpdateType('keyboard');
90+
});
91+
});
92+
});
93+
});

React/Modules/RCTKeyboardObserver.m

+2
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,14 @@ - (void)EVENT:(NSNotification *)notification \
110110
CGRect endFrame = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
111111
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
112112
UIViewAnimationCurve curve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
113+
NSInteger isLocalUserInfoKey = [userInfo[UIKeyboardIsLocalUserInfoKey] integerValue];
113114

114115
return @{
115116
@"startCoordinates": RCTRectDictionaryValue(beginFrame),
116117
@"endCoordinates": RCTRectDictionaryValue(endFrame),
117118
@"duration": @(duration * 1000.0), // ms
118119
@"easing": RCTAnimationNameForCurve(curve),
120+
@"isEventFromThisApp": isLocalUserInfoKey == 1 ? @YES : @NO,
119121
};
120122
#endif
121123
}

0 commit comments

Comments
 (0)