diff --git a/src/autocomplete/CommunityProvider.js b/src/autocomplete/CommunityProvider.js
index ffce1e71cf..992df0f773 100644
--- a/src/autocomplete/CommunityProvider.js
+++ b/src/autocomplete/CommunityProvider.js
@@ -105,8 +105,14 @@ export default class CommunityProvider extends AutocompleteProvider {
}
renderCompletions(completions: [React.Component]): ?React.Component {
- return
+
{ initialComponent }
{ title }
{ subtitle }
diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js
index 8afcba6ab0..1e39593022 100644
--- a/src/autocomplete/EmojiProvider.js
+++ b/src/autocomplete/EmojiProvider.js
@@ -116,7 +116,9 @@ export default class EmojiProvider extends AutocompleteProvider {
return {
completion: unicode,
component: (
-
{ unicode }} />
+ { unicode }
+ } />
),
range,
};
@@ -130,8 +132,10 @@ export default class EmojiProvider extends AutocompleteProvider {
}
renderCompletions(completions: [React.Component]): ?React.Component {
- return
- { completions }
-
;
+ return (
+
+ { completions }
+
+ );
}
}
diff --git a/src/autocomplete/NotifProvider.js b/src/autocomplete/NotifProvider.js
index 60a3352f9b..95cfb34616 100644
--- a/src/autocomplete/NotifProvider.js
+++ b/src/autocomplete/NotifProvider.js
@@ -58,8 +58,14 @@ export default class NotifProvider extends AutocompleteProvider {
}
renderCompletions(completions: [React.Component]): ?React.Component {
- return
- { completions }
-
;
+ return (
+
+ { completions }
+
+ );
}
}
diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.js
index b94edf590c..79986657b8 100644
--- a/src/autocomplete/RoomProvider.js
+++ b/src/autocomplete/RoomProvider.js
@@ -109,8 +109,14 @@ export default class RoomProvider extends AutocompleteProvider {
}
renderCompletions(completions: [React.Component]): ?React.Component {
- return
- { completions }
-
;
+ return (
+
+ { completions }
+
+ );
}
}
diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js
index 62ae5d4970..451ae0bb83 100644
--- a/src/autocomplete/UserProvider.js
+++ b/src/autocomplete/UserProvider.js
@@ -164,9 +164,11 @@ export default class UserProvider extends AutocompleteProvider {
}
renderCompletions(completions: [React.Component]): ?React.Component {
- return
- { completions }
-
;
+ return (
+
+ { completions }
+
+ );
}
shouldForceComplete(): boolean {
diff --git a/src/components/views/rooms/Autocomplete.js b/src/components/views/rooms/Autocomplete.js
index 243cfe2f75..ad5fa198a3 100644
--- a/src/components/views/rooms/Autocomplete.js
+++ b/src/components/views/rooms/Autocomplete.js
@@ -20,18 +20,17 @@ import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import flatMap from 'lodash/flatMap';
-import isEqual from 'lodash/isEqual';
-import sdk from '../../../index';
import type {Completion} from '../../../autocomplete/Autocompleter';
import Promise from 'bluebird';
import { Room } from 'matrix-js-sdk';
-import {getCompletions} from '../../../autocomplete/Autocompleter';
import SettingsStore from "../../../settings/SettingsStore";
import Autocompleter from '../../../autocomplete/Autocompleter';
const COMPOSER_SELECTED = 0;
+export const generateCompletionDomId = (number) => `mx_Autocomplete_Completion_${number}`;
+
export default class Autocomplete extends React.Component {
constructor(props) {
super(props);
@@ -224,7 +223,7 @@ export default class Autocomplete extends React.Component {
setSelection(selectionOffset: number) {
this.setState({selectionOffset, hide: false});
if (this.props.onSelectionChange) {
- this.props.onSelectionChange(this.state.completionList[selectionOffset - 1]);
+ this.props.onSelectionChange(this.state.completionList[selectionOffset - 1], selectionOffset - 1);
}
}
@@ -250,9 +249,8 @@ export default class Autocomplete extends React.Component {
let position = 1;
const renderedCompletions = this.state.completions.map((completionResult, i) => {
const completions = completionResult.completions.map((completion, i) => {
- const className = classNames('mx_Autocomplete_Completion', {
- 'selected': position === this.state.selectionOffset,
- });
+ const selected = position === this.state.selectionOffset;
+ const className = classNames('mx_Autocomplete_Completion', {selected});
const componentPosition = position;
position++;
@@ -261,10 +259,12 @@ export default class Autocomplete extends React.Component {
};
return React.cloneElement(completion.component, {
- key: i,
- ref: `completion${position - 1}`,
+ "key": i,
+ "ref": `completion${componentPosition}`,
+ "id": generateCompletionDomId(componentPosition - 1), // 0 index the completion IDs
className,
onClick,
+ "aria-selected": selected,
});
});
diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js
index 895696e118..4ec43d8af2 100644
--- a/src/components/views/rooms/BasicMessageComposer.js
+++ b/src/components/views/rooms/BasicMessageComposer.js
@@ -28,7 +28,7 @@ import {
replaceRangeAndMoveCaret,
} from '../../../editor/operations';
import {getCaretOffsetAndText, getRangeForSelection} from '../../../editor/dom';
-import Autocomplete from '../rooms/Autocomplete';
+import Autocomplete, {generateCompletionDomId} from '../rooms/Autocomplete';
import {autoCompleteCreator} from '../../../editor/parts';
import {parsePlainTextMessage} from '../../../editor/deserialize';
import {renderModel} from '../../../editor/render';
@@ -432,8 +432,9 @@ export default class BasicMessageEditor extends React.Component {
this.props.model.autoComplete.onComponentConfirm(completion);
}
- _onAutoCompleteSelectionChange = (completion) => {
+ _onAutoCompleteSelectionChange = (completion, completionIndex) => {
this.props.model.autoComplete.onComponentSelectionChange(completion);
+ this.setState({completionIndex});
}
componentWillUnmount() {
@@ -535,6 +536,8 @@ export default class BasicMessageEditor extends React.Component {
quote: ctrlShortcutLabel(">"),
};
+ const {completionIndex} = this.state;
+
return (
{ autoComplete }
this._formatBarRef = ref} onAction={this._onFormatAction} shortcuts={shortcuts} />
@@ -548,7 +551,13 @@ export default class BasicMessageEditor extends React.Component {
onKeyDown={this._onKeyDown}
ref={ref => this._editorRef = ref}
aria-label={this.props.label}
- >
+ role="textbox"
+ aria-multiline="true"
+ aria-autocomplete="both"
+ aria-haspopup="listbox"
+ aria-expanded={Boolean(this.state.autoComplete)}
+ aria-activedescendant={completionIndex >= 0 ? generateCompletionDomId(completionIndex) : undefined}
+ />
);
}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 5d99d37405..d18a1146b3 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1726,11 +1726,16 @@
"Clear personal data": "Clear personal data",
"Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.": "Warning: Your personal data (including encryption keys) is still stored on this device. Clear it if you're finished using this device, or want to sign in to another account.",
"Commands": "Commands",
+ "Community Autocomplete": "Community Autocomplete",
"Results from DuckDuckGo": "Results from DuckDuckGo",
"Emoji": "Emoji",
+ "Emoji Autocomplete": "Emoji Autocomplete",
"Notify the whole room": "Notify the whole room",
"Room Notification": "Room Notification",
+ "Notification Autocomplete": "Notification Autocomplete",
+ "Room Autocomplete": "Room Autocomplete",
"Users": "Users",
+ "User Autocomplete": "User Autocomplete",
"unknown device": "unknown device",
"NOT verified": "NOT verified",
"Blacklisted": "Blacklisted",