vnext/proposals/active/keyboard-reconcile-desktop.md
There are several APIs and behaviors around keyboarding introduced in Windows and macOS, some of which match and some don't. Windows implementation is a bit more complete. The ultimate goal is to reconcile and come up with a singular set of keyboarding APIs and behaviors for RN desktop that can then be proposed for upstreaming into core as well as documented cleanly. This document captures the set of APIs and behaviors and identifies:
Property that determines whether this View, Touchable should be focusable with a non-touch input device, eg. receive focus with a hardware keyboard. Setting the property to true will make that View compoonent become a part of tabbing loop.
| TYPE | REQUIRED | PLATFORM | DEFAULT |
|---|---|---|---|
| boolean | No | Windows, macOS | false |
React Native core introduced focusable that does the same thing as acceptsKeyboardFocus in v0.62. In order to align and make this complete, the work here includes the following:
focusable and marked acceptsKeyboardFocus as deprecated in 0.62.focusable and mark acceptsKeyboardFocus as deprecated. Tracked by - Issue#498focusable is already supported in Android and exists in core. There is no hard upstreaming requirement, however – it would be good to implement this for iOS/iPadOS for completion.React Native core supports .focus(), .blur() methods and onFocus(), onBlur() callbacks on TextInput component. Windows and macOS implementations add the same methods and callbacks on View in addition to supporting them on TextInput. The work remaining for these include:
isFocused() method that returns true/false based on whether this TextInput currently has focus. It will be useful to add this to View to and upstream as a separate PR. Tee this up with the previous one. Tracked by - windows Issue#5511, macOS Issue#499.focus(), .blur(), onFocus(), onBlur() and isFocused() to Pressable. Tracked by windows Issue#5589, macOS Issue#518Both Windows and macOS implementations have added appropriate keyboarding defaults to JS components like Button, Touchables. The implementation includes firing onPress() callbacks on Button and Touchable components when they are in focus and Enter/Space key is pressed. The work remaining for these include:
macOS has implemented an onScrollKeyDown callback in ScrollView to scroll the content when arrow key is pressed. Work to reconcile this includes:
onKeyDown, onKeyUp methods instead (see following sections). Tracked by - Issue#501The following callbacks are available on View component (and get passed through to TextInput and Pressable) in Windows to cover the most common use cases where key stroke handling is likely to occur. Other individual components where they may be neeeded can wrap a View around themselves.
Note: The
onKeyDownevent fires repeatedly when a key is held down continuously which is also similar to how native Windows implements KeyDown.
All the below need to be implemented for macOS. Tracked by Issue#520
| API | Args | Returns | Description |
|---|---|---|---|
| onKeyDown | IKeyboardEvent | void | Occurs when a keyboard key is pressed when a component has focus. On Windows, this corresponds to KeyDown |
| onKeyDownCapture | IKeyboardEvent | void | Occurs when the onKeyDown event is being routed. onKeyDown is the corresponding bubbling event. On Windows, this corresponds to PreviewKeyDown |
| onKeyUp | IKeyboardEvent | void | Occurs when a keyboard key is released when a component has focus. On Windows, this corresponds to KeyUp |
| onKeyUpCapture | IKeyboardEvent | void | Occurs when the onKeyUp event is being routed. onKeyUp is the corresponding bubbling event. On Windows, this corresponds to PreviewKeyUp |
Where IKeyboardEvent is a new event type added to ReactNative.NativeSyntheticEvents of type INativeKeyboardEvent. The properties in NativeSyntheticEvent like target, bubbles, cancelable etc., are also available for IKeyboardEvent and follow the same behaviors as other events in react-native today.
INativeKeyboardEvent is a new interface and will expose the following properties:
| Property | Type | Description | Default |
|---|---|---|---|
| key | string | The character typed by the user.TODO: Document the w3c spec for how the keys show up | string.Empty |
| altKey | boolean | The Alt (Alternative) key. Also maps to Apple Option key. | false |
| ctrlKey | boolean | The Ctrl (Control) key. | false |
| shiftKey | boolean | The Shift key. | false |
| metaKey | boolean | Maps to Windows Logo key and the Apple Command key. | False |
| repeat | boolean | Flag to represent if a key is being held down/repeated. Tracked by Windows Issue#5513, macOS Issue#502 | False |
| timestamp | number | The time, relative to the system boot time, in milliseconds. | undefined |
Where EventPhase is an enum to detect whether the keystroke is being tunneled/bubbled to the target component that has focus. It has the following fields:
In the following example, the lastKeyDown prop will contain the key stroke from the end user when keyboard focus is on View.
<View onKeyDown={this._onKeyDown} />
private _onKeyDown = (event: IKeyboardEvent) => {
this.setState({ lastKeyDown: event.nativeEvent.key });
};
To co-ordinate the handoffs of the onKeyXX events between the native layer and the JS layer, 2 corresponding properties on View and TextInput components are available. These are:
| Property | Type | Description |
|---|---|---|
| keyDownEvents | IHandledKeyboardEvents[] | Specifies the key(s) that are handled in the JS layer by the onKeyDown/onKeyDownCapture events |
| keyUpEvents | IHandledKeyboardEvents[] | Specifies the key(s) that are handled in the JS layer by the onKeyUp/onKeyUpCapture events |
Where IHandledKeyboardEvents is a new type which takes the following parameters:
When the onKeyXX events are handled by the app code, the corresponding native component will have KeyXX/PreviewKeyXX events marked as handled for the declared key strokes.
In the following example, the app's logic takes precedence when certain keystrokes are encountered at certain event routing phases in the TextInput before the native platform can handle them.
<TextInput onKeyUp={this._onKeyUp} keyUpEvents={handledNativeKeyboardEvents} />
const handledNativeKeyboardEvents: IHandledKeyboardEvent[] = [
{ key: 'Enter', eventPhase : EventPhase.Bubbling },
];
private _onKeyUp = (event: IKeyboardEvent) => {
if(event.nativeEvent.key == 'Enter'){
//do something custom when Enter key is detected when focus is on the TextInput component AFTER the native TextBox has had a chance to handle it (eventPhase = Bubbling)
}
};
Behavior details:
<TextInput onKeyUp={this._onKeyUp} keyUpEvents={handledNativeKeyboardEvents} />
const handledNativeKeyboardEvents: IHandledKeyboardEvent[] = [
{ key: 'Esc' },
{ key: 'Enter', ctrlKey : true, eventPhase : EventPhase.Capturing }
];
private _onKeyUp = (event: IKeyboardEvent) => {
if(event.nativeEvent.key == 'Esc'){
//do something custom when Escape key is detected when focus is on the TextInput component AFTER
//the native TextBox has had a chance to handle it (default eventPhase = Bubbling)
} else if (event.nativeEvent.key == 'Enter' &&
event.nativeEvent.eventPhase == EventPhase.Capturing &&
event.nativeEvent.ctrlKey == true)
{
//do something custom when user presses Ctrl + Enter when focus is on the TextInput component BEFORE
//the native TextBox has had a chance to handle it.
}
};