@samitouri / QOS-React-2 / commits / af032764a9

[react-events] Adds preventKeys support to Keyboard responder (#16642)

Dominic Gannaway committed Sep 4, 2019 at 01:38 UTC af032764a9f244633eb7ff1a3205d2ec6073b501
2 files changed +174 -3
packages/react-events/src/dom/Keyboard.js
+46 -3
@@ -22,6 +22,7 @@ type KeyboardProps = {
22 disabled: boolean,
23 onKeyDown: (e: KeyboardEvent) => void,
24 onKeyUp: (e: KeyboardEvent) => void,
25 + preventKeys: Array<string>,
26 };
27
28 type KeyboardEvent = {|
@@ -36,9 +37,12 @@ type KeyboardEvent = {|
37 target: Element | Document,
38 type: KeyboardEventType,
39 timeStamp: number,
40 + defaultPrevented: boolean,
41 |};
42
41 -const targetEventTypes = ['keydown', 'keyup'];
43 +const isArray = Array.isArray;
44 +const targetEventTypes = ['keydown_active', 'keyup'];
45 +const modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'];
46
47 /**
48 * Normalization of deprecated HTML5 `key` values
@@ -107,7 +111,7 @@ function isFunction(obj): boolean {
111 return typeof obj === 'function';
112 }
113
110 -function getEventKey(nativeEvent): string {
114 +function getEventKey(nativeEvent: Object): string {
115 const nativeKey = nativeEvent.key;
116 if (nativeKey) {
117 // Normalize inconsistent values reported by browsers due to
@@ -128,6 +132,7 @@ function createKeyboardEvent(
132 context: ReactDOMResponderContext,
133 type: KeyboardEventType,
134 target: Document | Element,
135 + defaultPrevented: boolean,
136 ): KeyboardEvent {
137 const nativeEvent = (event: any).nativeEvent;
138 const {
@@ -143,6 +148,7 @@ function createKeyboardEvent(
148 return {
149 altKey,
150 ctrlKey,
151 + defaultPrevented,
152 isComposing,
153 key: getEventKey(nativeEvent),
154 location,
@@ -161,8 +167,15 @@ function dispatchKeyboardEvent(
167 context: ReactDOMResponderContext,
168 type: KeyboardEventType,
169 target: Element | Document,
170 + defaultPrevented: boolean,
171 ): void {
165 - const syntheticEvent = createKeyboardEvent(event, context, type, target);
172 + const syntheticEvent = createKeyboardEvent(
173 + event,
174 + context,
175 + type,
176 + target,
177 + defaultPrevented,
178 + );
179 context.dispatchEvent(syntheticEvent, listener, DiscreteEvent);
180 }
181
@@ -174,11 +187,39 @@ const keyboardResponderImpl = {
187 props: KeyboardProps,
188 ): void {
189 const {responderTarget, type} = event;
190 + const nativeEvent: any = event.nativeEvent;
191
192 if (props.disabled) {
193 return;
194 }
195 + let defaultPrevented = nativeEvent.defaultPrevented === true;
196 if (type === 'keydown') {
197 + const preventKeys = props.preventKeys;
198 + if (!defaultPrevented && isArray(preventKeys)) {
199 + preventKeyLoop: for (let i = 0; i < preventKeys.length; i++) {
200 + const preventKey = preventKeys[i];
201 + let key = preventKey;
202 +
203 + if (isArray(preventKey)) {
204 + key = preventKey[0];
205 + const config = ((preventKey[1]: any): Object);
206 + for (let s = 0; s < modifiers.length; s++) {
207 + const modifier = modifiers[s];
208 + if (
209 + (config[modifier] && !nativeEvent[modifier]) ||
210 + (!config[modifier] && nativeEvent[modifier])
211 + ) {
212 + continue preventKeyLoop;
213 + }
214 + }
215 + }
216 + if (key === getEventKey(nativeEvent)) {
217 + defaultPrevented = true;
218 + nativeEvent.preventDefault();
219 + break;
220 + }
221 + }
222 + }
223 const onKeyDown = props.onKeyDown;
224 if (isFunction(onKeyDown)) {
225 dispatchKeyboardEvent(
@@ -187,6 +228,7 @@ const keyboardResponderImpl = {
228 context,
229 'keydown',
230 ((responderTarget: any): Element | Document),
231 + defaultPrevented,
232 );
233 }
234 } else if (type === 'keyup') {
@@ -198,6 +240,7 @@ const keyboardResponderImpl = {
240 context,
241 'keyup',
242 ((responderTarget: any): Element | Document),
243 + defaultPrevented,
244 );
245 }
246 }
packages/react-events/src/dom/__tests__/Keyboard-test.internal.js
+128
@@ -92,6 +92,134 @@ describe('Keyboard event responder', () => {
92 });
93 });
94
95 + describe('preventKeys', () => {
96 + it('onKeyDown is default prevented', () => {
97 + const onKeyDown = jest.fn();
98 + const ref = React.createRef();
99 + const Component = () => {
100 + const listener = useKeyboard({
101 + onKeyDown,
102 + preventKeys: ['Tab'],
103 + });
104 + return <div ref={ref} listeners={listener} />;
105 + };
106 + ReactDOM.render(<Component />, container);
107 +
108 + const preventDefault = jest.fn();
109 + const target = createEventTarget(ref.current);
110 + target.keydown({key: 'Tab', preventDefault});
111 + expect(onKeyDown).toHaveBeenCalledTimes(1);
112 + expect(preventDefault).toBeCalled();
113 + expect(onKeyDown).toHaveBeenCalledWith(
114 + expect.objectContaining({
115 + key: 'Tab',
116 + type: 'keydown',
117 + defaultPrevented: true,
118 + }),
119 + );
120 + });
121 +
122 + it('onKeyDown is default prevented (falsy modifier keys)', () => {
123 + let onKeyDown = jest.fn();
124 + let ref = React.createRef();
125 + let Component = () => {
126 + const listener = useKeyboard({
127 + onKeyDown,
128 + preventKeys: [['Tab', {metaKey: false}]],
129 + });
130 + return <div ref={ref} listeners={listener} />;
131 + };
132 + ReactDOM.render(<Component />, container);
133 +
134 + let preventDefault = jest.fn();
135 + let target = createEventTarget(ref.current);
136 + target.keydown({key: 'Tab', preventDefault, metaKey: true});
137 + expect(onKeyDown).toHaveBeenCalledTimes(1);
138 + expect(preventDefault).not.toBeCalled();
139 + expect(onKeyDown).toHaveBeenCalledWith(
140 + expect.objectContaining({
141 + key: 'Tab',
142 + type: 'keydown',
143 + defaultPrevented: false,
144 + }),
145 + );
146 +
147 + onKeyDown = jest.fn();
148 + ref = React.createRef();
149 + Component = () => {
150 + const listener = useKeyboard({
151 + onKeyDown,
152 + preventKeys: [['Tab', {metaKey: true}]],
153 + });
154 + return <div ref={ref} listeners={listener} />;
155 + };
156 + ReactDOM.render(<Component />, container);
157 +
158 + preventDefault = jest.fn();
159 + target = createEventTarget(ref.current);
160 + target.keydown({key: 'Tab', preventDefault, metaKey: false});
161 + expect(onKeyDown).toHaveBeenCalledTimes(1);
162 + expect(preventDefault).not.toBeCalled();
163 + expect(onKeyDown).toHaveBeenCalledWith(
164 + expect.objectContaining({
165 + key: 'Tab',
166 + type: 'keydown',
167 + defaultPrevented: false,
168 + }),
169 + );
170 + });
171 +
172 + it('onKeyDown is default prevented (truthy modifier keys)', () => {
173 + let onKeyDown = jest.fn();
174 + let ref = React.createRef();
175 + let Component = () => {
176 + const listener = useKeyboard({
177 + onKeyDown,
178 + preventKeys: [['Tab', {metaKey: true}]],
179 + });
180 + return <div ref={ref} listeners={listener} />;
181 + };
182 + ReactDOM.render(<Component />, container);
183 +
184 + let preventDefault = jest.fn();
185 + let target = createEventTarget(ref.current);
186 + target.keydown({key: 'Tab', preventDefault, metaKey: true});
187 + expect(onKeyDown).toHaveBeenCalledTimes(1);
188 + expect(preventDefault).toBeCalled();
189 + expect(onKeyDown).toHaveBeenCalledWith(
190 + expect.objectContaining({
191 + key: 'Tab',
192 + type: 'keydown',
193 + defaultPrevented: true,
194 + }),
195 + );
196 +
197 + onKeyDown = jest.fn();
198 + ref = React.createRef();
199 + Component = () => {
200 + const listener = useKeyboard({
201 + onKeyDown,
202 + preventKeys: [['Tab', {metaKey: false}]],
203 + });
204 + return <div ref={ref} listeners={listener} />;
205 + };
206 + ReactDOM.render(<Component />, container);
207 +
208 + preventDefault = jest.fn();
209 + target = createEventTarget(ref.current);
210 + target.keydown({key: 'Tab', preventDefault, metaKey: false});
211 + expect(onKeyDown).toHaveBeenCalledTimes(1);
212 + expect(preventDefault).toBeCalled();
213 + expect(onKeyDown).toHaveBeenCalledWith(
214 + expect.objectContaining({
215 + key: 'Tab',
216 + type: 'keydown',
217 + defaultPrevented: true,
218 + }),
219 + );
220 + });
221 + });
222 +
223 describe('onKeyUp', () => {
224 let onKeyDown, onKeyUp, ref;
225