aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2022-11-11 09:26:59 +0100
committersonartech <sonartech@sonarsource.com>2022-11-14 20:03:39 +0000
commit01c4dc5db1b4d64babbeb33e6a2d2be043fabeeb (patch)
tree01fe7598028829afc6f1b66b1b2ebfb1635e0ab6
parent1a9f524c359d8e1bc099f150d1ca53c237c8c848 (diff)
downloadsonarqube-01c4dc5db1b4d64babbeb33e6a2d2be043fabeeb.tar.gz
sonarqube-01c4dc5db1b4d64babbeb33e6a2d2be043fabeeb.zip
SONAR-17591 Fix keyboard arrow behavior in multi-line input
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx4
-rw-r--r--server/sonar-web/src/main/js/components/controls/UpDownKeyboardHandler.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx34
-rw-r--r--server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts4
4 files changed, 37 insertions, 11 deletions
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
index beaccb76e96..e309c9a4af8 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/SecurityHotspotsApp.tsx
@@ -27,6 +27,7 @@ import withCurrentUserContext from '../../app/components/current-user/withCurren
import { Location, Router, withRouter } from '../../components/hoc/withRouter';
import { getLeakValue } from '../../components/measure/utils';
import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../helpers/branch-like';
+import { isInput } from '../../helpers/keyboardEventHelpers';
import { KeyboardKeys } from '../../helpers/keycodes';
import { getStandards } from '../../helpers/security-standard';
import { BranchLike } from '../../types/branch-like';
@@ -147,6 +148,9 @@ export class SecurityHotspotsApp extends React.PureComponent<Props, State> {
}
handleKeyDown = (event: KeyboardEvent) => {
+ if (isInput(event)) {
+ return;
+ }
if (event.key === KeyboardKeys.Alt) {
event.preventDefault();
return;
diff --git a/server/sonar-web/src/main/js/components/controls/UpDownKeyboardHandler.tsx b/server/sonar-web/src/main/js/components/controls/UpDownKeyboardHandler.tsx
index faf70fc0ce6..874843ba02b 100644
--- a/server/sonar-web/src/main/js/components/controls/UpDownKeyboardHandler.tsx
+++ b/server/sonar-web/src/main/js/components/controls/UpDownKeyboardHandler.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import { isShortcut } from '../../helpers/keyboardEventHelpers';
+import { isShortcut, isTextarea } from '../../helpers/keyboardEventHelpers';
import { KeyboardKeys } from '../../helpers/keycodes';
interface Props {
@@ -47,7 +47,7 @@ export default class UpDownKeyboardHanlder extends React.PureComponent<
}
handleKeyboard = (event: KeyboardEvent) => {
- if (isShortcut(event)) {
+ if (isShortcut(event) || isTextarea(event)) {
return true;
}
switch (event.key) {
@@ -67,7 +67,7 @@ export default class UpDownKeyboardHanlder extends React.PureComponent<
getFocusableElement() {
const { containerClass = 'popup' } = this.props;
- const focussableElements = `.${containerClass} a,.${containerClass} button,.${containerClass} input[type=text]`;
+ const focussableElements = `.${containerClass} a,.${containerClass} button,.${containerClass} input[type=text],.${containerClass} textarea`;
return document.querySelectorAll<HTMLElement>(focussableElements);
}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx
index 3b476d48c72..b21c0d013f4 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/Toggler-test.tsx
@@ -36,6 +36,8 @@ const ui = {
outButton: byRole('button', { name: 'out' }),
overlayButton: byRole('button', { name: 'overlay' }),
nextOverlayButton: byRole('button', { name: 'next overlay' }),
+ overlayTextarea: byRole('textbox'),
+ overlayLatButton: byRole('button', { name: 'last' }),
};
async function openToggler(user: UserEvent) {
@@ -55,25 +57,40 @@ function focusOut() {
it('should handle key up/down', async () => {
const user = userEvent.setup({ delay: null });
- const rerender = renderToggler();
+ const rerender = renderToggler(
+ {},
+ <>
+ <textarea name="test-area" />
+ <button type="button">last</button>
+ </>
+ );
await openToggler(user);
await user.keyboard('{ArrowUp}');
- expect(ui.nextOverlayButton.get()).toHaveFocus();
+ expect(ui.overlayLatButton.get()).toHaveFocus();
- await user.keyboard('{ArrowDown}');
- expect(ui.overlayButton.get()).toHaveFocus();
+ await user.keyboard('{ArrowUp}');
+ expect(ui.overlayTextarea.get()).toHaveFocus();
+ // Focus does not escape multiline input
await user.keyboard('{ArrowDown}');
- expect(ui.nextOverlayButton.get()).toHaveFocus();
-
+ expect(ui.overlayTextarea.get()).toHaveFocus();
await user.keyboard('{ArrowUp}');
- expect(ui.overlayButton.get()).toHaveFocus();
+ expect(ui.overlayTextarea.get()).toHaveFocus();
+
+ // Escapt textarea
+ await user.keyboard('{Tab}');
// No focus change when using shortcut
await user.keyboard('{Control>}{ArrowUp}{/Control}');
+ expect(ui.overlayLatButton.get()).toHaveFocus();
+
+ await user.keyboard('{ArrowDown}');
expect(ui.overlayButton.get()).toHaveFocus();
+ await user.keyboard('{ArrowDown}');
+ expect(ui.nextOverlayButton.get()).toHaveFocus();
+
rerender();
await openToggler(user);
await user.keyboard('{ArrowDown}');
@@ -202,7 +219,7 @@ it('should open/close correctly when default props is applied', async () => {
expect(ui.overlayButton.query()).not.toBeInTheDocument();
});
-function renderToggler(override?: Partial<Toggler['props']>) {
+function renderToggler(override?: Partial<Toggler['props']>, additionalOverlay?: React.ReactNode) {
function App(props: Partial<Toggler['props']>) {
const [open, setOpen] = React.useState(false);
@@ -215,6 +232,7 @@ function renderToggler(override?: Partial<Toggler['props']>) {
<div className="popup">
<button type="button">overlay</button>
<button type="button">next overlay</button>
+ {additionalOverlay}
</div>
}
{...props}
diff --git a/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts b/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts
index fc156ad6da7..39b91dc4bc6 100644
--- a/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts
+++ b/server/sonar-web/src/main/js/helpers/keyboardEventHelpers.ts
@@ -21,6 +21,10 @@
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 isInput(event: KeyboardEvent): boolean {
const { tagName } = event.target as HTMLElement;