Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/e2e/threads/threads.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe("Threads", () => {
.click({ force: true }); // Cypress has no ability to hover
cy.get(".mx_EmojiPicker").within(() => {
cy.get('input[type="text"]').type("wave");
cy.contains('[role="menuitem"]', "👋").click();
cy.contains('[role="gridcell"]', "👋").click();
});

cy.get(".mx_ThreadView").within(() => {
Expand Down
8 changes: 8 additions & 0 deletions res/css/views/emojipicker/_EmojiPicker.pcss
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ limitations under the License.
list-style: none;
width: 38px;
cursor: pointer;

&:focus-within {
background-color: $focus-bg-color;
}
}

.mx_EmojiPicker_body .mx_EmojiPicker_item_wrapper[tabindex="0"] .mx_EmojiPicker_item {
background-color: $focus-bg-color;
}

.mx_EmojiPicker_item {
Expand Down
8 changes: 4 additions & 4 deletions src/accessibility/RovingTabIndex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface IState {
refs: Ref[];
}

interface IContext {
export interface IContext {
state: IState;
dispatch: Dispatch<IAction>;
}
Expand All @@ -80,7 +80,7 @@ export enum Type {
SetFocus = "SET_FOCUS",
}

interface IAction {
export interface IAction {
type: Type;
payload: {
ref: Ref;
Expand Down Expand Up @@ -160,7 +160,7 @@ interface IProps {
handleUpDown?: boolean;
handleLeftRight?: boolean;
children(renderProps: { onKeyDownHandler(ev: React.KeyboardEvent): void }): ReactNode;
onKeyDown?(ev: React.KeyboardEvent, state: IState): void;
onKeyDown?(ev: React.KeyboardEvent, state: IState, dispatch: Dispatch<IAction>): void;
}

export const findSiblingElement = (
Expand Down Expand Up @@ -199,7 +199,7 @@ export const RovingTabIndexProvider: React.FC<IProps> = ({
const onKeyDownHandler = useCallback(
(ev: React.KeyboardEvent) => {
if (onKeyDown) {
onKeyDown(ev, context.state);
onKeyDown(ev, context.state, context.dispatch);
if (ev.defaultPrevented) {
return;
}
Expand Down
13 changes: 12 additions & 1 deletion src/accessibility/roving/RovingAccessibleButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,17 @@ import { Ref } from "./types";

interface IProps extends Omit<React.ComponentProps<typeof AccessibleButton>, "inputRef" | "tabIndex"> {
inputRef?: Ref;
focusOnMouseOver?: boolean;
}

// Wrapper to allow use of useRovingTabIndex for simple AccessibleButtons outside of React Functional Components.
export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, onFocus, ...props }) => {
export const RovingAccessibleButton: React.FC<IProps> = ({
inputRef,
onFocus,
onMouseOver,
focusOnMouseOver,
...props
}) => {
const [onFocusInternal, isActive, ref] = useRovingTabIndex(inputRef);
return (
<AccessibleButton
Expand All @@ -34,6 +41,10 @@ export const RovingAccessibleButton: React.FC<IProps> = ({ inputRef, onFocus, ..
onFocusInternal();
onFocus?.(event);
}}
onMouseOver={(event: React.MouseEvent) => {
if (focusOnMouseOver) onFocusInternal();
onMouseOver?.(event);
}}
inputRef={ref}
tabIndex={isActive ? 0 : -1}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/ContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export default class ContextMenu extends React.PureComponent<React.PropsWithChil

const first =
element.querySelector<HTMLElement>('[role^="menuitem"]') ||
element.querySelector<HTMLElement>("[tab-index]");
element.querySelector<HTMLElement>("[tabindex]");

if (first) {
first.focus();
Expand Down
2 changes: 2 additions & 0 deletions src/components/views/elements/LazyRenderList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ interface IProps<T> {

element?: string;
className?: string;
role?: string;
}

interface IState {
Expand Down Expand Up @@ -128,6 +129,7 @@ export default class LazyRenderList<T = any> extends React.Component<IProps<T>,
const elementProps = {
style: { paddingTop: `${paddingTop}px`, paddingBottom: `${paddingBottom}px` },
className: this.props.className,
role: this.props.role,
};
return React.createElement(element, elementProps, renderedItems.map(renderItem));
}
Expand Down
22 changes: 19 additions & 3 deletions src/components/views/emojipicker/Category.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { CATEGORY_HEADER_HEIGHT, EMOJI_HEIGHT, EMOJIS_PER_ROW } from "./EmojiPic
import LazyRenderList from "../elements/LazyRenderList";
import { DATA_BY_CATEGORY, IEmoji } from "../../../emoji";
import Emoji from "./Emoji";
import { ButtonEvent } from "../elements/AccessibleButton";

const OVERFLOW_ROWS = 3;

Expand All @@ -42,18 +43,31 @@ interface IProps {
heightBefore: number;
viewportHeight: number;
scrollTop: number;
onClick(emoji: IEmoji): void;
onClick(ev: ButtonEvent, emoji: IEmoji): void;
onMouseEnter(emoji: IEmoji): void;
onMouseLeave(emoji: IEmoji): void;
isEmojiDisabled?: (unicode: string) => boolean;
}

function hexEncode(str: string): string {
let hex: string;
let i: number;

let result = "";
for (i = 0; i < str.length; i++) {
hex = str.charCodeAt(i).toString(16);
result += ("000" + hex).slice(-4);
}

return result;
}

class Category extends React.PureComponent<IProps> {
private renderEmojiRow = (rowIndex: number): JSX.Element => {
const { onClick, onMouseEnter, onMouseLeave, selectedEmojis, emojis } = this.props;
const emojisForRow = emojis.slice(rowIndex * 8, (rowIndex + 1) * 8);
return (
<div key={rowIndex}>
<div key={rowIndex} role="row">
{emojisForRow.map((emoji) => (
<Emoji
key={emoji.hexcode}
Expand All @@ -63,6 +77,8 @@ class Category extends React.PureComponent<IProps> {
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
disabled={this.props.isEmojiDisabled?.(emoji.unicode)}
id={`mx_EmojiPicker_item_${this.props.id}_${hexEncode(emoji.unicode)}`}
role="gridcell"
/>
))}
</div>
Expand Down Expand Up @@ -101,7 +117,6 @@ class Category extends React.PureComponent<IProps> {
>
<h2 className="mx_EmojiPicker_category_label">{name}</h2>
<LazyRenderList
element="ul"
className="mx_EmojiPicker_list"
itemHeight={EMOJI_HEIGHT}
items={rows}
Expand All @@ -110,6 +125,7 @@ class Category extends React.PureComponent<IProps> {
overflowItems={OVERFLOW_ROWS}
overflowMargin={0}
renderItem={this.renderEmojiRow}
role="grid"
/>
</section>
);
Expand Down
20 changes: 12 additions & 8 deletions src/components/views/emojipicker/Emoji.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,40 @@ limitations under the License.

import React from "react";

import { MenuItem } from "../../structures/ContextMenu";
import { IEmoji } from "../../../emoji";
import { ButtonEvent } from "../elements/AccessibleButton";
import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex";

interface IProps {
emoji: IEmoji;
selectedEmojis?: Set<string>;
onClick(emoji: IEmoji): void;
onClick(ev: ButtonEvent, emoji: IEmoji): void;
onMouseEnter(emoji: IEmoji): void;
onMouseLeave(emoji: IEmoji): void;
disabled?: boolean;
id?: string;
role?: string;
}

class Emoji extends React.PureComponent<IProps> {
public render(): React.ReactNode {
const { onClick, onMouseEnter, onMouseLeave, emoji, selectedEmojis } = this.props;
const isSelected = selectedEmojis && selectedEmojis.has(emoji.unicode);
const isSelected = selectedEmojis?.has(emoji.unicode);
return (
<MenuItem
element="li"
onClick={() => onClick(emoji)}
<RovingAccessibleButton
id={this.props.id}
onClick={(ev) => onClick(ev, emoji)}
onMouseEnter={() => onMouseEnter(emoji)}
onMouseLeave={() => onMouseLeave(emoji)}
className="mx_EmojiPicker_item_wrapper"
label={emoji.unicode}
disabled={this.props.disabled}
role={this.props.role}
focusOnMouseOver
>
<div className={`mx_EmojiPicker_item ${isSelected ? "mx_EmojiPicker_item_selected" : ""}`}>
{emoji.unicode}
</div>
</MenuItem>
</RovingAccessibleButton>
);
}
}
Expand Down
Loading