From e256dbc611dd786dbfea51a6fa2c9637e28c5b6e Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Mon, 14 Oct 2019 18:50:58 +0200 Subject: [PATCH] feat: Escape hotkey enters 'navigation mode' (#37167) * fix: escape to navigation mode more generally Previously only 'escape'ing from the editor prevented auto focus on the editor, now you can 'escape' from anywhere that listens to Hotkeys. Also the outline for HotKeys is hidden as it is not used by tab navigation. * feat: require escape to enter navigation mode --- .../Challenges/components/Hotkeys.js | 46 ++++++++++++++++--- .../Challenges/components/hotkeys.css | 4 ++ 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 client/src/templates/Challenges/components/hotkeys.css diff --git a/client/src/templates/Challenges/components/Hotkeys.js b/client/src/templates/Challenges/components/Hotkeys.js index 9a17a267af..c82a50a311 100644 --- a/client/src/templates/Challenges/components/Hotkeys.js +++ b/client/src/templates/Challenges/components/Hotkeys.js @@ -2,29 +2,48 @@ import React from 'react'; import PropTypes from 'prop-types'; import { HotKeys, GlobalHotKeys } from 'react-hotkeys'; import { navigate } from 'gatsby'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; + +import { canFocusEditorSelector, setEditorFocusability } from '../redux'; +import './hotkeys.css'; + +const mapStateToProps = createSelector( + canFocusEditorSelector, + canFocusEditor => ({ + canFocusEditor + }) +); + +const mapDispatchToProps = { setEditorFocusability }; const keyMap = { + NAVIGATION_MODE: 'escape', EXECUTE_CHALLENGE: ['ctrl+enter', 'command+enter'], NAVIGATE_PREV: ['p'], NAVIGATE_NEXT: ['n'] }; const propTypes = { + canFocusEditor: PropTypes.bool, children: PropTypes.any, executeChallenge: PropTypes.func, innerRef: PropTypes.any, introPath: PropTypes.string, nextChallengePath: PropTypes.string, - prevChallengePath: PropTypes.string + prevChallengePath: PropTypes.string, + setEditorFocusability: PropTypes.func.isRequired }; function Hotkeys({ + canFocusEditor, children, executeChallenge, introPath, innerRef, nextChallengePath, - prevChallengePath + prevChallengePath, + setEditorFocusability }) { const handlers = { EXECUTE_CHALLENGE: e => { @@ -35,14 +54,26 @@ function Hotkeys({ e.preventDefault(); if (executeChallenge) executeChallenge(); }, - NAVIGATE_PREV: () => navigate(prevChallengePath), - NAVIGATE_NEXT: () => navigate(introPath ? introPath : nextChallengePath) + NAVIGATION_MODE: () => setEditorFocusability(false), + NAVIGATE_PREV: () => { + if (!canFocusEditor) navigate(prevChallengePath); + }, + NAVIGATE_NEXT: () => { + if (!canFocusEditor) navigate(introPath ? introPath : nextChallengePath); + } }; // GlobalHotKeys is always mounted and tracks all keypresses. Without it, // keyup events can be missed and react-hotkeys assumes that that key is still // being pressed. + // allowChanges is necessary if the handlers depend on props (in this case + // canFocusEditor) return ( - + {children} @@ -52,4 +83,7 @@ function Hotkeys({ Hotkeys.displayName = 'Hotkeys'; Hotkeys.propTypes = propTypes; -export default Hotkeys; +export default connect( + mapStateToProps, + mapDispatchToProps +)(Hotkeys); diff --git a/client/src/templates/Challenges/components/hotkeys.css b/client/src/templates/Challenges/components/hotkeys.css new file mode 100644 index 0000000000..4ad9f9c48f --- /dev/null +++ b/client/src/templates/Challenges/components/hotkeys.css @@ -0,0 +1,4 @@ +/* hide the outline for the HotKeys component */ +div[tabindex='-1']:focus { + outline: none; +}