diff --git a/src/components/views/emojipicker/Category.js b/src/components/views/emojipicker/Category.js index 3c4352105e..eb3f83dcdf 100644 --- a/src/components/views/emojipicker/Category.js +++ b/src/components/views/emojipicker/Category.js @@ -67,7 +67,13 @@ class Category extends React.PureComponent { const localScrollTop = Math.max(0, scrollTop - listTop); return ( -
+

{name}

diff --git a/src/components/views/emojipicker/Emoji.js b/src/components/views/emojipicker/Emoji.js index 75f23c5761..36aa4ff782 100644 --- a/src/components/views/emojipicker/Emoji.js +++ b/src/components/views/emojipicker/Emoji.js @@ -16,6 +16,7 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import {MenuItem} from "../../structures/ContextMenu"; class Emoji extends React.PureComponent { static propTypes = { @@ -30,14 +31,18 @@ class Emoji extends React.PureComponent { const { onClick, onMouseEnter, onMouseLeave, emoji, selectedEmojis } = this.props; const isSelected = selectedEmojis && selectedEmojis.has(emoji.unicode); return ( -
  • onClick(emoji)} + onClick(emoji)} onMouseEnter={() => onMouseEnter(emoji)} onMouseLeave={() => onMouseLeave(emoji)} - className="mx_EmojiPicker_item_wrapper"> + className="mx_EmojiPicker_item_wrapper" + label={emoji.unicode} + >
    {emoji.unicode}
    -
  • + ); } } diff --git a/src/components/views/emojipicker/EmojiPicker.js b/src/components/views/emojipicker/EmojiPicker.js index cacc15a5f9..16a0fc67e7 100644 --- a/src/components/views/emojipicker/EmojiPicker.js +++ b/src/components/views/emojipicker/EmojiPicker.js @@ -147,8 +147,12 @@ class EmojiPicker extends React.Component { // We update this here instead of through React to avoid re-render on scroll. if (cat.visible) { cat.ref.current.classList.add("mx_EmojiPicker_anchor_visible"); + cat.ref.current.setAttribute("aria-selected", true); + cat.ref.current.setAttribute("tabindex", 0); } else { cat.ref.current.classList.remove("mx_EmojiPicker_anchor_visible"); + cat.ref.current.setAttribute("aria-selected", false); + cat.ref.current.setAttribute("tabindex", -1); } } } diff --git a/src/components/views/emojipicker/Header.js b/src/components/views/emojipicker/Header.js index b98e90e9b1..c53437e02d 100644 --- a/src/components/views/emojipicker/Header.js +++ b/src/components/views/emojipicker/Header.js @@ -16,23 +16,89 @@ limitations under the License. import React from 'react'; import PropTypes from 'prop-types'; +import classNames from "classnames"; + +import {_t} from "../../../languageHandler"; +import {Key} from "../../../Keyboard"; class Header extends React.PureComponent { static propTypes = { categories: PropTypes.arrayOf(PropTypes.object).isRequired, onAnchorClick: PropTypes.func.isRequired, - refs: PropTypes.object, + }; + + findNearestEnabled(index, delta) { + index += this.props.categories.length; + const cats = [...this.props.categories, ...this.props.categories, ...this.props.categories]; + + while (index < cats.length && index >= 0) { + if (cats[index].enabled) return index % this.props.categories.length; + index += delta > 0 ? 1 : -1; + } + } + + changeCategoryRelative(delta) { + const current = this.props.categories.findIndex(c => c.visible); + this.changeCategoryAbsolute(current + delta, delta); + } + + changeCategoryAbsolute(index, delta=1) { + const category = this.props.categories[this.findNearestEnabled(index, delta)]; + if (category) { + this.props.onAnchorClick(category.id); + category.ref.current.focus(); + } + } + + // Implements ARIA Tabs with Automatic Activation pattern + // https://www.w3.org/TR/wai-aria-practices/examples/tabs/tabs-1/tabs.html + onKeyDown = (ev) => { + let handled = true; + switch (ev.key) { + case Key.ARROW_LEFT: + this.changeCategoryRelative(-1); + break; + case Key.ARROW_RIGHT: + this.changeCategoryRelative(1); + break; + + case Key.HOME: + this.changeCategoryAbsolute(0); + break; + case Key.END: + this.changeCategoryAbsolute(this.props.categories.length - 1, -1); + break; + default: + handled = false; + } + + if (handled) { + ev.preventDefault(); + ev.stopPropagation(); + } }; render() { return ( - ); } diff --git a/src/components/views/emojipicker/QuickReactions.js b/src/components/views/emojipicker/QuickReactions.js index 0bc799d356..8a20a4659b 100644 --- a/src/components/views/emojipicker/QuickReactions.js +++ b/src/components/views/emojipicker/QuickReactions.js @@ -72,7 +72,7 @@ class QuickReactions extends React.Component { } -