Skip to content
This repository was archived by the owner on Nov 11, 2024. It is now read-only.

Commit 26b036d

Browse files
committed
feat: rework the RCTKeyCommands api
BREAKING CHANGE
1 parent 873b3a6 commit 26b036d

File tree

8 files changed

+198
-177
lines changed

8 files changed

+198
-177
lines changed

React/Base/RCTKeyCommands.h

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,56 @@
1-
/**
2-
* Copyright (c) 2015-present, Facebook, Inc.
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-
81
#import <AppKit/AppKit.h>
92

3+
#pragma mark - RCTKeyCommand
4+
5+
typedef unsigned short RCTKeyCode;
6+
7+
@interface RCTKeyCommand : NSObject
8+
9+
/// The upper or lower cased characters being pressed.
10+
@property (nonatomic, readonly) NSString *input;
11+
12+
/// The device-independent key code.
13+
@property (nonatomic, readonly) RCTKeyCode keyCode;
14+
15+
/// True for keydown events. False for keyup events.
16+
@property (nonatomic, readonly) BOOL isDown;
17+
18+
/// The modifiers being pressed. (eg: command, control, etc)
19+
@property (nonatomic, readonly) NSEventModifierFlags flags;
20+
21+
/// The window that received the original NSEvent.
22+
@property (nonatomic, readonly) NSWindow *window;
23+
24+
/// The original NSEvent that triggered this command.
25+
@property (nonatomic, readonly) NSEvent *event;
26+
27+
- (BOOL)matchesInput:(NSString *)input;
28+
- (BOOL)matchesInput:(NSString *)input flags:(NSEventModifierFlags)flags;
29+
30+
- (BOOL)matchesKeyCode:(RCTKeyCode)keyCode;
31+
- (BOOL)matchesKeyCode:(RCTKeyCode)keyCode flags:(NSEventModifierFlags)flags;
32+
33+
- (void)preventDefault;
34+
- (BOOL)isDefaultPrevented;
35+
36+
@end
37+
38+
#pragma mark - RCTKeyCommandObserver
39+
40+
@protocol RCTKeyCommandObserver <NSObject>
41+
42+
- (void)observeKeyCommand:(RCTKeyCommand *)command;
43+
44+
@end
45+
46+
#pragma mark - RCTKeyCommands
47+
1048
@interface RCTKeyCommands : NSObject
1149

1250
+ (instancetype)sharedInstance;
1351

14-
/**
15-
* Register a single-press keyboard command.
16-
*/
17-
- (void)registerKeyCommandWithInput:(NSString *)input
18-
modifierFlags:(NSEventModifierFlags)flags
19-
action:(void (^)(NSEvent *command))block;
20-
21-
/**
22-
* Unregister a single-press keyboard command.
23-
*/
24-
- (void)unregisterKeyCommandWithInput:(NSString *)input
25-
modifierFlags:(NSEventModifierFlags)flags;
26-
27-
/**
28-
* Check if a single-press command is registered.
29-
*/
30-
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
31-
modifierFlags:(NSEventModifierFlags)flags;
52+
- (void)addObserver:(NSObject<RCTKeyCommandObserver> *)observer;
53+
54+
- (void)removeObserver:(NSObject<RCTKeyCommandObserver> *)observer;
3255

3356
@end

React/Base/RCTKeyCommands.m

Lines changed: 74 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,89 @@
1-
/**
2-
* Copyright (c) 2015-present, Facebook, Inc.
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-
81
#import "RCTKeyCommands.h"
9-
10-
#import <AppKit/AppKit.h>
11-
122
#import "RCTDefines.h"
133
#import "RCTUtils.h"
144

15-
@interface RCTKeyCommand : NSObject <NSCopying>
16-
17-
@property (nonatomic, strong) NSString *keyCommand;
18-
@property (nonatomic) NSEventModifierFlags modifierFlags;
19-
@property (nonatomic, copy) void (^block)(NSEvent *);
20-
21-
@end
22-
235
@implementation RCTKeyCommand
6+
{
7+
BOOL _preventDefault;
8+
}
249

25-
- (instancetype)initWithKeyCommand:(NSString *)keyCommand
26-
modifierFlags:(NSEventModifierFlags)modifierFlags
27-
block:(void (^)(NSEvent *))block
10+
- (instancetype)initWithEvent:(NSEvent *)event
2811
{
2912
if ((self = [super init])) {
30-
_keyCommand = keyCommand;
31-
_modifierFlags = modifierFlags;
32-
_block = block;
13+
_event = event;
3314
}
3415
return self;
3516
}
3617

3718
RCT_NOT_IMPLEMENTED(- (instancetype)init)
3819

39-
- (id)copyWithZone:(__unused NSZone *)zone
20+
- (NSString *)input
4021
{
41-
return self;
22+
return _event.characters;
4223
}
4324

44-
- (NSUInteger)hash
25+
- (unsigned short)keyCode
4526
{
46-
return _keyCommand.hash ^ _modifierFlags;
27+
return _event.keyCode;
4728
}
4829

49-
- (BOOL)isEqual:(RCTKeyCommand *)object
30+
- (BOOL)isDown
5031
{
51-
if (![object isKindOfClass:[RCTKeyCommand class]]) {
52-
return NO;
53-
}
54-
return [self matchesInput:object.keyCommand
55-
flags:object.modifierFlags];
32+
return _event.type == NSEventTypeKeyDown;
5633
}
5734

58-
- (BOOL)matchesInput:(NSString*)keyCommand flags:(int)flags
35+
- (NSEventModifierFlags)flags
5936
{
60-
return [_keyCommand isEqual:keyCommand] && _modifierFlags == flags;
37+
return _event.modifierFlags & NSEventModifierFlagDeviceIndependentFlagsMask;
6138
}
6239

63-
- (NSString *)description
40+
- (NSWindow *)window
6441
{
65-
return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd hasBlock=%@>",
66-
[self class], self, _keyCommand, _modifierFlags,
67-
_block ? @"YES" : @"NO"];
42+
return _event.window;
6843
}
6944

70-
@end
71-
72-
@interface RCTKeyCommands ()
45+
- (NSString *)description
46+
{
47+
return [NSString stringWithFormat:@"<%@:%p input=\"%@\" flags=%zd isDown=%@>",
48+
[self class], self, self.input, self.flags, self.isDown ? @"YES" : @"NO"];
49+
}
7350

74-
@property (nonatomic, strong) NSMutableSet<RCTKeyCommand *> *commands;
51+
- (BOOL)matchesInput:(NSString *)input
52+
{
53+
return [self matchesInput:input flags:0];
54+
}
7555

76-
@end
56+
- (BOOL)matchesInput:(NSString *)input flags:(NSEventModifierFlags)flags
57+
{
58+
return [self.input isEqualToString:input] && self.flags == flags;
59+
}
7760

61+
- (BOOL)matchesKeyCode:(RCTKeyCode)keyCode
62+
{
63+
return [self matchesKeyCode:keyCode flags:0];
64+
}
7865

79-
@implementation NSWindow (RCTKeyCommands)
66+
- (BOOL)matchesKeyCode:(RCTKeyCode)keyCode flags:(NSEventModifierFlags)flags
67+
{
68+
return self.keyCode == keyCode && self.flags == flags;
69+
}
8070

81-
- (void)keyDown:(NSEvent *)theEvent
71+
- (void)preventDefault
8272
{
83-
for (RCTKeyCommand *command in [RCTKeyCommands sharedInstance].commands) {
84-
if ([command.keyCommand isEqualToString:theEvent.characters] &&
85-
command.modifierFlags == (theEvent.modifierFlags & NSDeviceIndependentModifierFlagsMask)) {
86-
if (command.block) {
87-
command.block(theEvent);
88-
}
89-
return;
90-
}
91-
}
73+
_preventDefault = YES;
74+
}
9275

93-
[super keyDown:theEvent];
76+
- (BOOL)isDefaultPrevented
77+
{
78+
return _preventDefault;
9479
}
9580

9681
@end
9782

9883
@implementation RCTKeyCommands
84+
{
85+
NSHashTable<id<RCTKeyCommandObserver>> *_observers;
86+
}
9987

10088
+ (instancetype)sharedInstance
10189
{
@@ -111,46 +99,52 @@ + (instancetype)sharedInstance
11199
- (instancetype)init
112100
{
113101
if ((self = [super init])) {
114-
_commands = [NSMutableSet new];
102+
_observers = [NSHashTable weakObjectsHashTable];
115103
}
116104
return self;
117105
}
118106

119-
- (void)registerKeyCommandWithInput:(NSString *)input
120-
modifierFlags:(NSEventModifierFlags)flags
121-
action:(void (^)(NSEvent *))block
107+
- (void)addObserver:(NSObject<RCTKeyCommandObserver> *)observer
122108
{
123109
RCTAssertMainQueue();
124-
125-
RCTKeyCommand *keyCommand = [[RCTKeyCommand alloc] initWithKeyCommand:input modifierFlags:flags block:block];
126-
[_commands removeObject:keyCommand];
127-
[_commands addObject:keyCommand];
110+
[_observers addObject:observer];
128111
}
129112

130-
- (void)unregisterKeyCommandWithInput:(NSString *)input
131-
modifierFlags:(NSEventModifierFlags)flags
113+
- (void)removeObserver:(NSObject<RCTKeyCommandObserver> *)observer
132114
{
133115
RCTAssertMainQueue();
116+
[_observers removeObject:observer];
117+
}
134118

135-
for (RCTKeyCommand *command in _commands.allObjects) {
136-
if ([command matchesInput:input flags:flags]) {
137-
[_commands removeObject:command];
138-
break;
139-
}
119+
- (BOOL)observeEvent:(NSEvent *)event
120+
{
121+
RCTAssertMainQueue();
122+
RCTKeyCommand *command = [[RCTKeyCommand alloc] initWithEvent:event];
123+
for (id<RCTKeyCommandObserver> observer in _observers) {
124+
[observer observeKeyCommand:command];
140125
}
126+
return command.isDefaultPrevented;
141127
}
142128

143-
- (BOOL)isKeyCommandRegisteredForInput:(NSString *)input
144-
modifierFlags:(NSEventModifierFlags)flags
129+
@end
130+
131+
@implementation NSWindow (RCTKeyCommands)
132+
133+
- (void)keyDown:(NSEvent *)event
145134
{
146-
RCTAssertMainQueue();
135+
BOOL isDefaultPrevented = [[RCTKeyCommands sharedInstance] observeEvent:event];
136+
if (!isDefaultPrevented) {
137+
[super keyDown:event];
138+
}
139+
}
147140

148-
for (RCTKeyCommand *command in _commands) {
149-
if ([command matchesInput:input flags:flags]) {
150-
return YES;
151-
}
141+
- (void)keyUp:(NSEvent *)event
142+
{
143+
BOOL isDefaultPrevented = [[RCTKeyCommands sharedInstance] observeEvent:event];
144+
if (!isDefaultPrevented) {
145+
[super keyUp:event];
152146
}
153-
return NO;
154147
}
155148

156149
@end
150+

React/Base/RCTReloadCommand.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,3 @@
1515

1616
/** Registers a weakly-held observer of the Command+R reload key command. */
1717
RCT_EXTERN void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener);
18-
19-
/** Triggers a reload for all current listeners. You shouldn't need to use this directly in most cases. */
20-
RCT_EXTERN void RCTTriggerReloadCommandListeners(void);

React/Base/RCTReloadCommand.m

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,44 @@
1010
#import "RCTAssert.h"
1111
#import "RCTKeyCommands.h"
1212

13-
/** main queue only */
14-
static NSHashTable<id<RCTReloadListener>> *listeners;
13+
@interface RCTReloadCommand : NSObject <RCTKeyCommandObserver>
1514

16-
void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
15+
@property (nonatomic, strong) NSHashTable<id<RCTReloadListener>> *listeners;
16+
17+
@end
18+
19+
@implementation RCTReloadCommand
20+
21+
- (instancetype)init
1722
{
18-
RCTAssertMainQueue(); // because registerKeyCommandWithInput: must be called on the main thread
19-
static dispatch_once_t onceToken;
20-
dispatch_once(&onceToken, ^{
21-
listeners = [NSHashTable weakObjectsHashTable];
22-
[[RCTKeyCommands sharedInstance] registerKeyCommandWithInput:@"r"
23-
modifierFlags:NSEventModifierFlagCommand
24-
action:
25-
^(__unused NSEvent *command) {
26-
RCTTriggerReloadCommandListeners();
27-
}];
28-
});
29-
[listeners addObject:listener];
23+
if (self = [super init]) {
24+
_listeners = [NSHashTable weakObjectsHashTable];
25+
26+
[RCTKeyCommands.sharedInstance addObserver:self];
27+
}
28+
return self;
3029
}
3130

32-
void RCTTriggerReloadCommandListeners(void)
31+
- (void)observeKeyCommand:(RCTKeyCommand *)command
3332
{
34-
RCTAssertMainQueue();
35-
// Copy to protect against mutation-during-enumeration.
36-
// If listeners hasn't been initialized yet we get nil, which works just fine.
37-
NSArray<id<RCTReloadListener>> *copiedListeners = [listeners allObjects];
38-
for (id<RCTReloadListener> l in copiedListeners) {
39-
[l didReceiveReloadCommand];
33+
if (command.isDown && [command matchesInput:@"r" flags:NSEventModifierFlagCommand]) {
34+
for (id<RCTReloadListener> listener in _listeners.allObjects) {
35+
[listener didReceiveReloadCommand];
36+
}
4037
}
4138
}
39+
40+
@end
41+
42+
void RCTRegisterReloadCommandListener(id<RCTReloadListener> listener)
43+
{
44+
RCTAssertMainQueue();
45+
46+
static RCTReloadCommand *command;
47+
static dispatch_once_t onceToken;
48+
dispatch_once(&onceToken, ^{
49+
command = [RCTReloadCommand new];
50+
});
51+
52+
[command.listeners addObject:listener];
53+
}

React/Base/RCTRootView.m

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
#import "RCTBridge.h"
1616
#import "RCTBridge+Private.h"
1717
#import "RCTEventDispatcher.h"
18-
#import "RCTKeyCommands.h"
1918
#import "RCTLog.h"
2019
#import "RCTPerformanceLogger.h"
2120
#import "RCTProfile.h"

0 commit comments

Comments
 (0)