diff --git a/React/Views/RCTView.h b/React/Views/RCTView.h index d1e40a0923c743..7afa57cff2088d 100644 --- a/React/Views/RCTView.h +++ b/React/Views/RCTView.h @@ -25,6 +25,10 @@ extern const UIAccessibilityTraits SwitchAccessibilityTrait; - (BOOL)becomeFirstResponder; - (BOOL)resignFirstResponder; + +#if TARGET_OS_OSX +- (BOOL)handleKeyboardEvent:(NSEvent *)event; +#endif // ]TODO(OSS Candidate ISS#2710739) /** diff --git a/React/Views/RCTView.m b/React/Views/RCTView.m index 653d0efde61f7a..9999bb086a9f74 100644 --- a/React/Views/RCTView.m +++ b/React/Views/RCTView.m @@ -1573,177 +1573,38 @@ - (BOOL)performDragOperation:(id )sender #pragma mark - Keyboard Events #if TARGET_OS_OSX -NSString* const leftArrowPressKey = @"ArrowLeft"; -NSString* const rightArrowPressKey = @"ArrowRight"; -NSString* const upArrowPressKey = @"ArrowUp"; -NSString* const downArrowPressKey = @"ArrowDown"; - -- (RCTViewKeyboardEvent*)keyboardEvent:(NSEvent*)event downPress:(BOOL)downPress { - // modifiers - BOOL capsLockKey = NO; - BOOL shiftKey = NO; - BOOL controlKey = NO; - BOOL optionKey = NO; - BOOL commandKey = NO; - BOOL numericPadKey = NO; - BOOL helpKey = NO; - BOOL functionKey = NO; - // commonly used key short-cuts - BOOL leftArrowKey = NO; - BOOL rightArrowKey = NO; - BOOL upArrowKey = NO; - BOOL downArrowKey = NO; - BOOL tabKeyPressed = NO; - BOOL escapeKeyPressed = NO; - NSString *key = event.charactersIgnoringModifiers; - if ([key length] == 0) { - return nil; - } - unichar const code = [key characterAtIndex:0]; - - // detect arrow key presses - if (code == NSLeftArrowFunctionKey) { - leftArrowKey = YES; - } else if (code == NSRightArrowFunctionKey) { - rightArrowKey = YES; - } else if (code == NSUpArrowFunctionKey) { - upArrowKey = YES; - } else if (code == NSDownArrowFunctionKey) { - downArrowKey = YES; - } - - // detect special key presses via the key code - switch (event.keyCode) { - case 48: // Tab - tabKeyPressed = YES; - break; - case 53: // Escape - escapeKeyPressed = YES; - break; - default: - break; +- (RCTViewKeyboardEvent*)keyboardEvent:(NSEvent*)event { + BOOL keyDown = event.type == NSEventTypeKeyDown; + NSArray *validKeys = keyDown ? self.validKeysDown : self.validKeysUp; + NSString *key = [RCTViewKeyboardEvent keyFromEvent:event]; + + // Only post events for keys we care about + if (![validKeys containsObject:key]) { + return nil; } - // detect modifier flags - if (event.modifierFlags & NSEventModifierFlagCapsLock) { - capsLockKey = YES; - } else if (event.modifierFlags & NSEventModifierFlagShift) { - shiftKey = YES; - } else if (event.modifierFlags & NSEventModifierFlagControl) { - controlKey = YES; - } else if (event.modifierFlags & NSEventModifierFlagOption) { - optionKey = YES; - } else if (event.modifierFlags & NSEventModifierFlagCommand) { - commandKey = YES; - } else if (event.modifierFlags & NSEventModifierFlagNumericPad) { - numericPadKey = YES; - } else if (event.modifierFlags & NSEventModifierFlagHelp) { - helpKey = YES; - } else if (event.modifierFlags & NSEventModifierFlagFunction) { - functionKey = YES; - } - - RCTViewKeyboardEvent *keyboardEvent = nil; - // only post events for keys we care about - if (downPress) { - NSString *keyToReturn = [self keyIsValid:key left:leftArrowKey right:rightArrowKey up:upArrowKey down:downArrowKey tabKey:tabKeyPressed escapeKey:escapeKeyPressed validKeys:[self validKeysDown]]; - if (keyToReturn != nil) { - keyboardEvent = [RCTViewKeyboardEvent keyDownEventWithReactTag:self.reactTag - capsLockKey:capsLockKey - shiftKey:shiftKey - ctrlKey:controlKey - altKey:optionKey - metaKey:commandKey - numericPadKey:numericPadKey - helpKey:helpKey - functionKey:functionKey - leftArrowKey:leftArrowKey - rightArrowKey:rightArrowKey - upArrowKey:upArrowKey - downArrowKey:downArrowKey - key:keyToReturn]; - } - } else { - NSString *keyToReturn = [self keyIsValid:key left:leftArrowKey right:rightArrowKey up:upArrowKey down:downArrowKey tabKey:tabKeyPressed escapeKey:escapeKeyPressed validKeys:[self validKeysUp]]; - if (keyToReturn != nil) { - keyboardEvent = [RCTViewKeyboardEvent keyUpEventWithReactTag:self.reactTag - capsLockKey:capsLockKey - shiftKey:shiftKey - ctrlKey:controlKey - altKey:optionKey - metaKey:commandKey - numericPadKey:numericPadKey - helpKey:helpKey - functionKey:functionKey - leftArrowKey:leftArrowKey - rightArrowKey:rightArrowKey - upArrowKey:upArrowKey - downArrowKey:downArrowKey - key:keyToReturn]; + return [RCTViewKeyboardEvent keyEventFromEvent:event reactTag:self.reactTag]; +} + +- (BOOL)handleKeyboardEvent:(NSEvent *)event { + if (event.type == NSEventTypeKeyDown ? self.onKeyDown : self.onKeyUp) { + RCTViewKeyboardEvent *keyboardEvent = [self keyboardEvent:event]; + if (keyboardEvent) { + [_eventDispatcher sendEvent:keyboardEvent]; + return YES; } } - return keyboardEvent; -} - -// check if the user typed key matches a key we need to send an event for -// translate key codes over to JS compatible keys -- (NSString*)keyIsValid:(NSString*)key left:(BOOL)leftArrowPressed right:(BOOL)rightArrowPressed up:(BOOL)upArrowPressed down:(BOOL)downArrowPressed tabKey:(BOOL)tabKeyPressed escapeKey:(BOOL)escapeKeyPressed validKeys:(NSArray*)validKeys { - NSString *keyToReturn = key; - - // Allow the flexibility of defining special keys in multiple ways - BOOL enterKeyValidityCheck = [key isEqualToString:@"\r"] && ([validKeys containsObject:@"Enter"] || [validKeys containsObject:@"\r"]); - BOOL tabKeyValidityCheck = tabKeyPressed && ([validKeys containsObject:@"Tab"]); // tab has to be checked via a key code so we can't just use the key itself here - BOOL escapeKeyValidityCheck = escapeKeyPressed && ([validKeys containsObject:@"Esc"] || [validKeys containsObject:@"Escape"]); // escape has to be checked via a key code so we can't just use the key itself here - BOOL leftArrowValidityCheck = [validKeys containsObject:leftArrowPressKey] && leftArrowPressed; - BOOL rightArrowValidityCheck = [validKeys containsObject:rightArrowPressKey] && rightArrowPressed; - BOOL upArrowValidityCheck = [validKeys containsObject:upArrowPressKey] && upArrowPressed; - BOOL downArrowValidityCheck = [validKeys containsObject:downArrowPressKey] && downArrowPressed; - -if (tabKeyValidityCheck) { - keyToReturn = @"Tab"; - } else if (escapeKeyValidityCheck) { - keyToReturn = @"Escape"; - } else if (enterKeyValidityCheck) { - keyToReturn = @"Enter"; - } else if (leftArrowValidityCheck) { - keyToReturn = leftArrowPressKey; - } else if (rightArrowValidityCheck) { - keyToReturn = rightArrowPressKey; - } else if (upArrowValidityCheck) { - keyToReturn = upArrowPressKey; - } else if (downArrowValidityCheck) { - keyToReturn = downArrowPressKey; - } else if (![validKeys containsObject:key]) { - keyToReturn = nil; - } - - return keyToReturn; + return NO; } - (void)keyDown:(NSEvent *)event { - if (self.onKeyDown == nil) { - [super keyDown:event]; - return; - } - - RCTViewKeyboardEvent *keyboardEvent = [self keyboardEvent:event downPress:YES]; - if (keyboardEvent != nil) { - [_eventDispatcher sendEvent:keyboardEvent]; - } else { + if (![self handleKeyboardEvent:event]) { [super keyDown:event]; } } - (void)keyUp:(NSEvent *)event { - if (self.onKeyUp == nil) { - [super keyUp:event]; - return; - } - - RCTViewKeyboardEvent *keyboardEvent = [self keyboardEvent:event downPress:NO]; - if (keyboardEvent != nil) { - [_eventDispatcher sendEvent:keyboardEvent]; - } else { + if (![self handleKeyboardEvent:event]) { [super keyUp:event]; } } diff --git a/React/Views/RCTViewKeyboardEvent.h b/React/Views/RCTViewKeyboardEvent.h index 0a91c72683c0d9..25d35441dcf32b 100644 --- a/React/Views/RCTViewKeyboardEvent.h +++ b/React/Views/RCTViewKeyboardEvent.h @@ -7,33 +7,11 @@ #import @interface RCTViewKeyboardEvent : RCTComponentEvent -+ (instancetype)keyDownEventWithReactTag:(NSNumber *)reactTag - capsLockKey:(BOOL)capsLockKey - shiftKey:(BOOL)shiftKey - ctrlKey:(BOOL)controlKey - altKey:(BOOL)optionKey - metaKey:(BOOL)commandKey - numericPadKey:(BOOL)numericPadKey - helpKey:(BOOL)helpKey - functionKey:(BOOL)functionKey - leftArrowKey:(BOOL)leftArrowKey - rightArrowKey:(BOOL)rightArrowKey - upArrowKey:(BOOL)upArrowKey - downArrowKey:(BOOL)downArrowKey - key:(NSString *)key; -+ (instancetype)keyUpEventWithReactTag:(NSNumber *)reactTag - capsLockKey:(BOOL)capsLockKey - shiftKey:(BOOL)shiftKey - ctrlKey:(BOOL)controlKey - altKey:(BOOL)optionKey - metaKey:(BOOL)commandKey - numericPadKey:(BOOL)numericPadKey - helpKey:(BOOL)helpKey - functionKey:(BOOL)functionKey - leftArrowKey:(BOOL)leftArrowKey - rightArrowKey:(BOOL)rightArrowKey - upArrowKey:(BOOL)upArrowKey - downArrowKey:(BOOL)downArrowKey - key:(NSString *)key; +#if TARGET_OS_OSX // TODO(macOS GH#774) ++ (NSDictionary *)bodyFromEvent:(NSEvent *)event; ++ (NSString *)keyFromEvent:(NSEvent *)event; ++ (instancetype)keyEventFromEvent:(NSEvent *)event reactTag:(NSNumber *)reactTag; +#endif // TODO(macOS GH#774) + @end diff --git a/React/Views/RCTViewKeyboardEvent.m b/React/Views/RCTViewKeyboardEvent.m index e1f44054170698..4d051b8cd9f319 100644 --- a/React/Views/RCTViewKeyboardEvent.m +++ b/React/Views/RCTViewKeyboardEvent.m @@ -9,69 +9,66 @@ #import @implementation RCTViewKeyboardEvent -// Keyboard mappings are aligned cross-platform as much as possible as per this doc https://github.com/microsoft/react-native-windows/blob/master/vnext/proposals/active/keyboard-reconcile-desktop.md -+ (instancetype)keyDownEventWithReactTag:(NSNumber *)reactTag - capsLockKey:(BOOL)capsLockKey - shiftKey:(BOOL)shiftKey - ctrlKey:(BOOL)controlKey - altKey:(BOOL)optionKey - metaKey:(BOOL)commandKey - numericPadKey:(BOOL)numericPadKey - helpKey:(BOOL)helpKey - functionKey:(BOOL)functionKey - leftArrowKey:(BOOL)leftArrowKey - rightArrowKey:(BOOL)rightArrowKey - upArrowKey:(BOOL)upArrowKey - downArrowKey:(BOOL)downArrowKey - key:(NSString *)key { - RCTViewKeyboardEvent *event = [[self alloc] initWithName:@"keyDown" - viewTag:reactTag - body:@{ @"capsLockKey" : @(capsLockKey), - @"shiftKey" : @(shiftKey), - @"ctrlKey" : @(controlKey), - @"altKey" : @(optionKey), - @"metaKey" : @(commandKey), - @"numericPadKey" : @(numericPadKey), - @"helpKey" : @(helpKey), - @"functionKey" : @(functionKey), - @"ArrowLeft" : @(leftArrowKey), - @"ArrowRight" : @(rightArrowKey), - @"ArrowUp" : @(upArrowKey), - @"ArrowDown" : @(downArrowKey), - @"key" : key }]; - return event; + +#if TARGET_OS_OSX // TODO(macOS GH#774) ++ (NSDictionary *)bodyFromEvent:(NSEvent *)event +{ + NSString *key = [self keyFromEvent:event]; + NSEventModifierFlags modifierFlags = event.modifierFlags; + + return @{ + @"key" : key, + @"capsLockKey" : (modifierFlags & NSEventModifierFlagCapsLock) ? @YES : @NO, + @"shiftKey" : (modifierFlags & NSEventModifierFlagShift) ? @YES : @NO, + @"ctrlKey" : (modifierFlags & NSEventModifierFlagControl) ? @YES : @NO, + @"altKey" : (modifierFlags & NSEventModifierFlagOption) ? @YES : @NO, + @"metaKey" : (modifierFlags & NSEventModifierFlagCommand) ? @YES : @NO, + @"numericPadKey" : (modifierFlags & NSEventModifierFlagNumericPad) ? @YES : @NO, + @"helpKey" : (modifierFlags & NSEventModifierFlagHelp) ? @YES : @NO, + @"functionKey" : (modifierFlags & NSEventModifierFlagFunction) ? @YES : @NO, + }; } -+(instancetype)keyUpEventWithReactTag:(NSNumber *)reactTag - capsLockKey:(BOOL)capsLockKey - shiftKey:(BOOL)shiftKey - ctrlKey:(BOOL)controlKey - altKey:(BOOL)optionKey - metaKey:(BOOL)commandKey - numericPadKey:(BOOL)numericPadKey - helpKey:(BOOL)helpKey - functionKey:(BOOL)functionKey - leftArrowKey:(BOOL)leftArrowKey - rightArrowKey:(BOOL)rightArrowKey - upArrowKey:(BOOL)upArrowKey - downArrowKey:(BOOL)downArrowKey - key:(NSString *)key { - RCTViewKeyboardEvent *event = [[self alloc] initWithName:@"keyUp" - viewTag:reactTag - body:@{ @"capsLockKey" : @(capsLockKey), - @"shiftKey" : @(shiftKey), - @"ctrlKey" : @(controlKey), - @"altKey" : @(optionKey), - @"metaKey" : @(commandKey), - @"numericPadKey" : @(numericPadKey), - @"helpKey" : @(helpKey), - @"functionKey" : @(functionKey), - @"ArrowLeft" : @(leftArrowKey), - @"ArrowRight" : @(rightArrowKey), - @"ArrowUp" : @(upArrowKey), - @"ArrowDown" : @(downArrowKey), - @"key" : key }]; - return event; ++ (NSString *)keyFromEvent:(NSEvent *)event +{ + NSString *key = event.charactersIgnoringModifiers; + unichar const code = key.length > 0 ? [key characterAtIndex:0] : 0; + + if (event.keyCode == 48) { + return @"Tab"; + } else if (event.keyCode == 53) { + return @"Escape"; + } else if (code == NSEnterCharacter || code == NSNewlineCharacter || code == NSCarriageReturnCharacter) { + return @"Enter"; + } else if (code == NSLeftArrowFunctionKey) { + return @"ArrowLeft"; + } else if (code == NSRightArrowFunctionKey) { + return @"ArrowRight"; + } else if (code == NSUpArrowFunctionKey) { + return @"ArrowUp"; + } else if (code == NSDownArrowFunctionKey) { + return @"ArrowDown"; + } else if (code == NSBackspaceCharacter || code == NSDeleteCharacter) { + return @"Backspace"; + } else if (code == NSDeleteFunctionKey) { + return @"Delete"; + } + + return key; +} + +// Keyboard mappings are aligned cross-platform as much as possible as per this doc https://github.com/microsoft/react-native-windows/blob/master/vnext/proposals/active/keyboard-reconcile-desktop.md ++ (instancetype)keyEventFromEvent:(NSEvent *)event reactTag:(NSNumber *)reactTag +{ + // Ignore "dead keys" (key press that waits for another key to make a character) + if (!event.charactersIgnoringModifiers.length) { + return nil; + } + + return [[self alloc] initWithName:(event.type == NSEventTypeKeyDown ? @"keyDown" : @"keyUp") + viewTag:reactTag + body:[self bodyFromEvent:event]]; } +#endif // TODO(macOS GH#774) @end diff --git a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js index eb9a22b56b0465..18f27318b02458 100644 --- a/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js +++ b/packages/rn-tester/js/examples/KeyboardEventsExample/KeyboardEventsExample.js @@ -53,7 +53,7 @@ class KeyEventExample extends React.Component<{}, State> { {Platform.OS === 'macos' ? (