]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18415 Fix day picker keyboard navigation triggering page shortcut due to React...
author7PH <benjamin.raymond@sonarsource.com>
Wed, 15 Feb 2023 11:03:56 +0000 (12:03 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 20 Feb 2023 20:03:00 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx
server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts

index 79dcc48d547807a8e60755845dda13309d52e93b..a95747b6b5ee53f75e9d5510a207527253d1a823 100644 (file)
@@ -32,7 +32,7 @@ import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { Location, Router, withRouter } from '../../../components/hoc/withRouter';
 import BackIcon from '../../../components/icons/BackIcon';
 import '../../../components/search-navigator.css';
-import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
+import { isDatePicker, isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
 import { KeyboardKeys } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import {
@@ -150,13 +150,19 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
   }
 
   attachShortcuts = () => {
-    document.addEventListener('keydown', this.handleKeyPress);
+    document.addEventListener('keydown', this.handleKeyDown);
   };
 
-  handleKeyPress = (event: KeyboardEvent) => {
+  handleKeyDown = (event: KeyboardEvent) => {
     if (isInput(event) || isShortcut(event)) {
-      return true;
+      return;
     }
+
+    // Ignore if date picker is open (to be removed when upgrading to React 17+)
+    if (isDatePicker(event)) {
+      return;
+    }
+
     switch (event.key) {
       case KeyboardKeys.LeftArrow:
         event.preventDefault();
@@ -178,7 +184,7 @@ export class CodingRulesApp extends React.PureComponent<Props, State> {
   };
 
   detachShortcuts = () => {
-    document.removeEventListener('keydown', this.handleKeyPress);
+    document.removeEventListener('keydown', this.handleKeyDown);
   };
 
   getOpenRule = (rules: Rule[]) => {
index 35c82364238ee443efe5783559b6804179d768e5..81b9a54f4e3d203930a375da6f95cd5cdb43872f 100644 (file)
@@ -53,7 +53,7 @@ import {
 } from '../../../helpers/branch-like';
 import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication';
 import { parseIssueFromResponse } from '../../../helpers/issues';
-import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
+import { isDatePicker, isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
 import { KeyboardKeys } from '../../../helpers/keycodes';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import {
@@ -265,7 +265,12 @@ export class App extends React.PureComponent<Props, State> {
     }
 
     if (isInput(event) || isShortcut(event)) {
-      return true;
+      return;
+    }
+
+    // Ignore if date picker is open (to be removed when upgrading to React 17+)
+    if (isDatePicker(event)) {
+      return;
     }
 
     if (event.key === KeyboardKeys.Alt) {
index a1d91211d0a24fa3e39f0e141f9f53dfdb4f17b2..2298c3b6de27fcc362720d842d36d5f82fc79363 100644 (file)
 export function isShortcut(event: KeyboardEvent): boolean {
   return event.ctrlKey || event.metaKey;
 }
-export function isTextarea(event: KeyboardEvent): boolean {
-  const { tagName } = event.target as HTMLElement;
-  return ['TEXTAREA'].includes(tagName);
+
+export function isTextarea(
+  event: KeyboardEvent
+): event is KeyboardEvent & { target: HTMLTextAreaElement } {
+  return event.target instanceof HTMLTextAreaElement;
+}
+
+export function isInput(
+  event: KeyboardEvent
+): event is KeyboardEvent & { target: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement } {
+  return (
+    event.target instanceof HTMLInputElement ||
+    event.target instanceof HTMLSelectElement ||
+    event.target instanceof HTMLTextAreaElement
+  );
 }
 
-export function isInput(event: KeyboardEvent): boolean {
-  const { tagName } = event.target as HTMLElement;
-  return ['INPUT', 'SELECT', 'TEXTAREA'].includes(tagName);
+/*
+ * Due to React 16 event delegation, stopPropagation called within react-day-picker is NOT preventing other event handlers from being called.
+ * As a temporary workaround, we detect this special case using this utility function.
+ * This utility function can be removed once we upgrade to React 17, since although there is still event delegation,
+ * it is delegated up to the React root, which will stop propagation before it reaches document event handlers.
+ */
+export function isDatePicker(event: KeyboardEvent): boolean {
+  return event.target instanceof Element && event.target.matches('.rdp-day');
 }