From: 7PH Date: Wed, 15 Feb 2023 11:03:56 +0000 (+0100) Subject: SONAR-18415 Fix day picker keyboard navigation triggering page shortcut due to React... X-Git-Tag: 10.0.0.68432~223 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1c63efbcabb47819bdc6e5f4797e7be3a1ead550;p=sonarqube.git SONAR-18415 Fix day picker keyboard navigation triggering page shortcut due to React 16 event delegation --- diff --git a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx index 79dcc48d547..a95747b6b5e 100644 --- a/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx +++ b/server/sonar-web/src/main/js/apps/coding-rules/components/CodingRulesApp.tsx @@ -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 { } 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 { }; detachShortcuts = () => { - document.removeEventListener('keydown', this.handleKeyPress); + document.removeEventListener('keydown', this.handleKeyDown); }; getOpenRule = (rules: Rule[]) => { diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 35c82364238..81b9a54f4e3 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -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 { } 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) { diff --git a/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts b/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts index a1d91211d0a..2298c3b6de2 100644 --- a/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts +++ b/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts @@ -20,12 +20,29 @@ 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'); }