fix(client): store accessibility for screenreaders (#42996)
* fix: store accessibility for screenreaders * Apply suggestions from code review Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * fix: remove old comments * feat: at short cut announcement when running test * feat: announce that accesibility mode is turned on * fix: use Redux values * Apply suggestions from code review Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * Apply suggestions from Oliver Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * fix: "rip out redux stuff" and use store instead * Apply suggestions from code review Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * Apply suggestions from code review Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com> * fix: old use of props * Update client/src/templates/Challenges/classic/editor.tsx Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -8,9 +8,7 @@ import {
|
|||||||
canFocusEditorSelector,
|
canFocusEditorSelector,
|
||||||
consoleOutputSelector,
|
consoleOutputSelector,
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
inAccessibilityModeSelector,
|
|
||||||
saveEditorContent,
|
saveEditorContent,
|
||||||
setAccessibilityMode,
|
|
||||||
setEditorFocusability,
|
setEditorFocusability,
|
||||||
visibleEditorsSelector,
|
visibleEditorsSelector,
|
||||||
updateFile
|
updateFile
|
||||||
@@ -31,7 +29,6 @@ const propTypes = {
|
|||||||
executeChallenge: PropTypes.func.isRequired,
|
executeChallenge: PropTypes.func.isRequired,
|
||||||
ext: PropTypes.string,
|
ext: PropTypes.string,
|
||||||
fileKey: PropTypes.string,
|
fileKey: PropTypes.string,
|
||||||
inAccessibilityMode: PropTypes.bool.isRequired,
|
|
||||||
initialEditorContent: PropTypes.string,
|
initialEditorContent: PropTypes.string,
|
||||||
initialExt: PropTypes.string,
|
initialExt: PropTypes.string,
|
||||||
output: PropTypes.arrayOf(PropTypes.string),
|
output: PropTypes.arrayOf(PropTypes.string),
|
||||||
@@ -40,7 +37,6 @@ const propTypes = {
|
|||||||
onResize: PropTypes.func
|
onResize: PropTypes.func
|
||||||
}),
|
}),
|
||||||
saveEditorContent: PropTypes.func.isRequired,
|
saveEditorContent: PropTypes.func.isRequired,
|
||||||
setAccessibilityMode: PropTypes.func.isRequired,
|
|
||||||
setEditorFocusability: PropTypes.func,
|
setEditorFocusability: PropTypes.func,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
@@ -57,21 +53,12 @@ const mapStateToProps = createSelector(
|
|||||||
visibleEditorsSelector,
|
visibleEditorsSelector,
|
||||||
canFocusEditorSelector,
|
canFocusEditorSelector,
|
||||||
consoleOutputSelector,
|
consoleOutputSelector,
|
||||||
inAccessibilityModeSelector,
|
|
||||||
isDonationModalOpenSelector,
|
isDonationModalOpenSelector,
|
||||||
userSelector,
|
userSelector,
|
||||||
(
|
(visibleEditors, canFocus, output, open, { theme = 'default' }) => ({
|
||||||
visibleEditors,
|
|
||||||
canFocus,
|
|
||||||
output,
|
|
||||||
accessibilityMode,
|
|
||||||
open,
|
|
||||||
{ theme = 'default' }
|
|
||||||
) => ({
|
|
||||||
visibleEditors,
|
visibleEditors,
|
||||||
canFocus: open ? false : canFocus,
|
canFocus: open ? false : canFocus,
|
||||||
output,
|
output,
|
||||||
inAccessibilityMode: accessibilityMode,
|
|
||||||
theme
|
theme
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -79,7 +66,6 @@ const mapStateToProps = createSelector(
|
|||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
saveEditorContent,
|
saveEditorContent,
|
||||||
setAccessibilityMode,
|
|
||||||
setEditorFocusability,
|
setEditorFocusability,
|
||||||
updateFile
|
updateFile
|
||||||
};
|
};
|
||||||
|
@@ -16,7 +16,7 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import store from 'store';
|
||||||
import { Loader } from '../../../components/helpers';
|
import { Loader } from '../../../components/helpers';
|
||||||
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
|
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
|
||||||
import {
|
import {
|
||||||
@@ -27,14 +27,13 @@ import {
|
|||||||
ResizePropsType,
|
ResizePropsType,
|
||||||
TestType
|
TestType
|
||||||
} from '../../../redux/prop-types';
|
} from '../../../redux/prop-types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
canFocusEditorSelector,
|
canFocusEditorSelector,
|
||||||
consoleOutputSelector,
|
consoleOutputSelector,
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
inAccessibilityModeSelector,
|
|
||||||
saveEditorContent,
|
saveEditorContent,
|
||||||
setEditorFocusability,
|
setEditorFocusability,
|
||||||
setAccessibilityMode,
|
|
||||||
updateFile,
|
updateFile,
|
||||||
challengeTestsSelector,
|
challengeTestsSelector,
|
||||||
submitChallenge
|
submitChallenge
|
||||||
@@ -55,13 +54,11 @@ interface EditorProps {
|
|||||||
executeChallenge: (isShouldCompletionModalOpen?: boolean) => void;
|
executeChallenge: (isShouldCompletionModalOpen?: boolean) => void;
|
||||||
ext: ExtTypes;
|
ext: ExtTypes;
|
||||||
fileKey: FileKeyTypes;
|
fileKey: FileKeyTypes;
|
||||||
inAccessibilityMode: boolean;
|
|
||||||
initialEditorContent: string;
|
initialEditorContent: string;
|
||||||
initialExt: string;
|
initialExt: string;
|
||||||
output: string[];
|
output: string[];
|
||||||
resizeProps: ResizePropsType;
|
resizeProps: ResizePropsType;
|
||||||
saveEditorContent: () => void;
|
saveEditorContent: () => void;
|
||||||
setAccessibilityMode: (isAccessible: boolean) => void;
|
|
||||||
setEditorFocusability: (isFocusable: boolean) => void;
|
setEditorFocusability: (isFocusable: boolean) => void;
|
||||||
submitChallenge: () => void;
|
submitChallenge: () => void;
|
||||||
tests: TestType[];
|
tests: TestType[];
|
||||||
@@ -100,21 +97,18 @@ interface EditorPropertyStore {
|
|||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
canFocusEditorSelector,
|
canFocusEditorSelector,
|
||||||
consoleOutputSelector,
|
consoleOutputSelector,
|
||||||
inAccessibilityModeSelector,
|
|
||||||
isDonationModalOpenSelector,
|
isDonationModalOpenSelector,
|
||||||
userSelector,
|
userSelector,
|
||||||
challengeTestsSelector,
|
challengeTestsSelector,
|
||||||
(
|
(
|
||||||
canFocus: boolean,
|
canFocus: boolean,
|
||||||
output: string[],
|
output: string[],
|
||||||
accessibilityMode: boolean,
|
|
||||||
open,
|
open,
|
||||||
{ theme = 'default' }: { theme: string },
|
{ theme = 'default' }: { theme: string },
|
||||||
tests: [{ text: string; testString: string }]
|
tests: [{ text: string; testString: string }]
|
||||||
) => ({
|
) => ({
|
||||||
canFocus: open ? false : canFocus,
|
canFocus: open ? false : canFocus,
|
||||||
output,
|
output,
|
||||||
inAccessibilityMode: accessibilityMode,
|
|
||||||
theme,
|
theme,
|
||||||
tests
|
tests
|
||||||
})
|
})
|
||||||
@@ -125,7 +119,6 @@ const mapStateToProps = createSelector(
|
|||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
saveEditorContent,
|
saveEditorContent,
|
||||||
setAccessibilityMode,
|
|
||||||
setEditorFocusability,
|
setEditorFocusability,
|
||||||
updateFile,
|
updateFile,
|
||||||
submitChallenge
|
submitChallenge
|
||||||
@@ -297,12 +290,31 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
// TODO this should *probably* be set on focus
|
// TODO this should *probably* be set on focus
|
||||||
editorRef.current = editor;
|
editorRef.current = editor;
|
||||||
data.editor = editor;
|
data.editor = editor;
|
||||||
|
|
||||||
|
const storedAccessibilityMode = () => {
|
||||||
|
const accessibility = store.get('accessibilityMode') as boolean;
|
||||||
|
if (!accessibility) {
|
||||||
|
store.set('accessibilityMode', false);
|
||||||
|
}
|
||||||
|
// Only able to set the arialabel when accessibility mode is set to true
|
||||||
|
// Otherwise it gets overwritten by the monaco default aria-label
|
||||||
|
if (accessibility) {
|
||||||
|
editor.updateOptions({
|
||||||
|
ariaLabel:
|
||||||
|
'Accessibility mode set to true. Press Ctrl+e to disable or press Alt+F1 for more options'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessibility;
|
||||||
|
};
|
||||||
|
|
||||||
|
const accessibilityMode = storedAccessibilityMode();
|
||||||
editor.updateOptions({
|
editor.updateOptions({
|
||||||
accessibilitySupport: props.inAccessibilityMode ? 'on' : 'auto'
|
accessibilitySupport: accessibilityMode ? 'on' : 'auto'
|
||||||
});
|
});
|
||||||
// Users who are using screen readers should not have to move focus from
|
// Users who are using screen readers should not have to move focus from
|
||||||
// the editor to the description every time they open a challenge.
|
// the editor to the description every time they open a challenge.
|
||||||
if (props.canFocus && !props.inAccessibilityMode) {
|
if (props.canFocus && !accessibilityMode) {
|
||||||
// TODO: only one Editor should be calling for focus at once.
|
// TODO: only one Editor should be calling for focus at once.
|
||||||
editor.focus();
|
editor.focus();
|
||||||
} else focusOnHotkeys();
|
} else focusOnHotkeys();
|
||||||
@@ -343,28 +355,18 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
editor.addAction({
|
editor.addAction({
|
||||||
id: 'toggle-accessibility',
|
id: 'toggle-accessibility',
|
||||||
label: 'Toggle Accessibility Mode',
|
label: 'Toggle Accessibility Mode',
|
||||||
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F1],
|
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_E],
|
||||||
run: () => {
|
run: () => {
|
||||||
const currentAccessibility = props.inAccessibilityMode;
|
const currentAccessibility = storedAccessibilityMode();
|
||||||
// The store needs to be updated first, as onDidChangeConfiguration is
|
|
||||||
// called before updateOptions returns
|
store.set('accessibilityMode', !currentAccessibility);
|
||||||
props.setAccessibilityMode(!currentAccessibility);
|
|
||||||
editor.updateOptions({
|
editor.updateOptions({
|
||||||
accessibilitySupport: currentAccessibility ? 'auto' : 'on'
|
accessibilitySupport: storedAccessibilityMode() ? 'on' : 'auto',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
editor.onDidFocusEditorWidget(() => props.setEditorFocusability(true));
|
editor.onDidFocusEditorWidget(() => props.setEditorFocusability(true));
|
||||||
// This is to persist changes caused by the accessibility tooltip.
|
|
||||||
editor.onDidChangeConfiguration(event => {
|
|
||||||
if (
|
|
||||||
event.hasChanged(monaco.editor.EditorOption.accessibilitySupport) &&
|
|
||||||
editor.getRawOptions().accessibilitySupport === 'on' &&
|
|
||||||
!props.inAccessibilityMode
|
|
||||||
) {
|
|
||||||
props.setAccessibilityMode(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const editableBoundaries = getEditableRegion();
|
const editableBoundaries = getEditableRegion();
|
||||||
|
|
||||||
|
@@ -53,7 +53,12 @@ function ToolPanel({
|
|||||||
isMobile ? 'tool-panel-group-mobile' : ''
|
isMobile ? 'tool-panel-group-mobile' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Button block={true} bsStyle='primary' onClick={handleRunTests}>
|
<Button
|
||||||
|
aria-label='Run the tests use shortcut Ctrl+enter'
|
||||||
|
block={true}
|
||||||
|
bsStyle='primary'
|
||||||
|
onClick={handleRunTests}
|
||||||
|
>
|
||||||
{isMobile ? t('buttons.run') : t('buttons.run-test')}
|
{isMobile ? t('buttons.run') : t('buttons.run-test')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
@@ -41,8 +41,7 @@ export const actionTypes = createTypes(
|
|||||||
'moveToTab',
|
'moveToTab',
|
||||||
|
|
||||||
'setEditorFocusability',
|
'setEditorFocusability',
|
||||||
'toggleVisibleEditor',
|
'toggleVisibleEditor'
|
||||||
'setAccessibilityMode'
|
|
||||||
],
|
],
|
||||||
ns
|
ns
|
||||||
);
|
);
|
||||||
|
@@ -31,7 +31,6 @@ const initialState = {
|
|||||||
challengeTests: [],
|
challengeTests: [],
|
||||||
consoleOut: [],
|
consoleOut: [],
|
||||||
hasCompletedBlock: false,
|
hasCompletedBlock: false,
|
||||||
inAccessibilityMode: false,
|
|
||||||
isCodeLocked: false,
|
isCodeLocked: false,
|
||||||
isBuildEnabled: true,
|
isBuildEnabled: true,
|
||||||
logsOut: [],
|
logsOut: [],
|
||||||
@@ -131,9 +130,6 @@ export const setEditorFocusability = createAction(
|
|||||||
export const toggleVisibleEditor = createAction(
|
export const toggleVisibleEditor = createAction(
|
||||||
actionTypes.toggleVisibleEditor
|
actionTypes.toggleVisibleEditor
|
||||||
);
|
);
|
||||||
export const setAccessibilityMode = createAction(
|
|
||||||
actionTypes.setAccessibilityMode
|
|
||||||
);
|
|
||||||
|
|
||||||
export const currentTabSelector = state => state[ns].currentTab;
|
export const currentTabSelector = state => state[ns].currentTab;
|
||||||
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
export const challengeFilesSelector = state => state[ns].challengeFiles;
|
||||||
@@ -211,9 +207,6 @@ export const challengeDataSelector = state => {
|
|||||||
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
|
export const canFocusEditorSelector = state => state[ns].canFocusEditor;
|
||||||
export const visibleEditorsSelector = state => state[ns].visibleEditors;
|
export const visibleEditorsSelector = state => state[ns].visibleEditors;
|
||||||
|
|
||||||
export const inAccessibilityModeSelector = state =>
|
|
||||||
state[ns].inAccessibilityMode;
|
|
||||||
|
|
||||||
export const reducer = handleActions(
|
export const reducer = handleActions(
|
||||||
{
|
{
|
||||||
[actionTypes.createFiles]: (state, { payload }) => ({
|
[actionTypes.createFiles]: (state, { payload }) => ({
|
||||||
@@ -361,11 +354,7 @@ export const reducer = handleActions(
|
|||||||
[payload]: !state.visibleEditors[payload]
|
[payload]: !state.visibleEditors[payload]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
[actionTypes.setAccessibilityMode]: (state, { payload }) => ({
|
|
||||||
...state,
|
|
||||||
inAccessibilityMode: payload
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
initialState
|
initialState
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user