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
This commit is contained in:
committed by
mrugesh
parent
26eacf6f47
commit
e256dbc611
@ -2,29 +2,48 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { HotKeys, GlobalHotKeys } from 'react-hotkeys';
|
import { HotKeys, GlobalHotKeys } from 'react-hotkeys';
|
||||||
import { navigate } from 'gatsby';
|
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 = {
|
const keyMap = {
|
||||||
|
NAVIGATION_MODE: 'escape',
|
||||||
EXECUTE_CHALLENGE: ['ctrl+enter', 'command+enter'],
|
EXECUTE_CHALLENGE: ['ctrl+enter', 'command+enter'],
|
||||||
NAVIGATE_PREV: ['p'],
|
NAVIGATE_PREV: ['p'],
|
||||||
NAVIGATE_NEXT: ['n']
|
NAVIGATE_NEXT: ['n']
|
||||||
};
|
};
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
canFocusEditor: PropTypes.bool,
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
executeChallenge: PropTypes.func,
|
executeChallenge: PropTypes.func,
|
||||||
innerRef: PropTypes.any,
|
innerRef: PropTypes.any,
|
||||||
introPath: PropTypes.string,
|
introPath: PropTypes.string,
|
||||||
nextChallengePath: PropTypes.string,
|
nextChallengePath: PropTypes.string,
|
||||||
prevChallengePath: PropTypes.string
|
prevChallengePath: PropTypes.string,
|
||||||
|
setEditorFocusability: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function Hotkeys({
|
function Hotkeys({
|
||||||
|
canFocusEditor,
|
||||||
children,
|
children,
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
introPath,
|
introPath,
|
||||||
innerRef,
|
innerRef,
|
||||||
nextChallengePath,
|
nextChallengePath,
|
||||||
prevChallengePath
|
prevChallengePath,
|
||||||
|
setEditorFocusability
|
||||||
}) {
|
}) {
|
||||||
const handlers = {
|
const handlers = {
|
||||||
EXECUTE_CHALLENGE: e => {
|
EXECUTE_CHALLENGE: e => {
|
||||||
@ -35,14 +54,26 @@ function Hotkeys({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (executeChallenge) executeChallenge();
|
if (executeChallenge) executeChallenge();
|
||||||
},
|
},
|
||||||
NAVIGATE_PREV: () => navigate(prevChallengePath),
|
NAVIGATION_MODE: () => setEditorFocusability(false),
|
||||||
NAVIGATE_NEXT: () => navigate(introPath ? introPath : nextChallengePath)
|
NAVIGATE_PREV: () => {
|
||||||
|
if (!canFocusEditor) navigate(prevChallengePath);
|
||||||
|
},
|
||||||
|
NAVIGATE_NEXT: () => {
|
||||||
|
if (!canFocusEditor) navigate(introPath ? introPath : nextChallengePath);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
// GlobalHotKeys is always mounted and tracks all keypresses. Without it,
|
// GlobalHotKeys is always mounted and tracks all keypresses. Without it,
|
||||||
// keyup events can be missed and react-hotkeys assumes that that key is still
|
// keyup events can be missed and react-hotkeys assumes that that key is still
|
||||||
// being pressed.
|
// being pressed.
|
||||||
|
// allowChanges is necessary if the handlers depend on props (in this case
|
||||||
|
// canFocusEditor)
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers} innerRef={innerRef} keyMap={keyMap}>
|
<HotKeys
|
||||||
|
allowChanges={true}
|
||||||
|
handlers={handlers}
|
||||||
|
innerRef={innerRef}
|
||||||
|
keyMap={keyMap}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
<GlobalHotKeys />
|
<GlobalHotKeys />
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
@ -52,4 +83,7 @@ function Hotkeys({
|
|||||||
Hotkeys.displayName = 'Hotkeys';
|
Hotkeys.displayName = 'Hotkeys';
|
||||||
Hotkeys.propTypes = propTypes;
|
Hotkeys.propTypes = propTypes;
|
||||||
|
|
||||||
export default Hotkeys;
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(Hotkeys);
|
||||||
|
4
client/src/templates/Challenges/components/hotkeys.css
Normal file
4
client/src/templates/Challenges/components/hotkeys.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/* hide the outline for the HotKeys component */
|
||||||
|
div[tabindex='-1']:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
Reference in New Issue
Block a user