Merge pull request #3895 from matrix-org/t3chguy/roving
Fix roving room list for resizer and ff tabstop a11y
This commit is contained in:
commit
2a331c0a2b
6 changed files with 66 additions and 73 deletions
|
@ -129,7 +129,7 @@ const reducer = (state, action) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RovingTabIndexProvider = ({children, handleHomeEnd}) => {
|
export const RovingTabIndexProvider = ({children, handleHomeEnd, onKeyDown}) => {
|
||||||
const [state, dispatch] = useReducer(reducer, {
|
const [state, dispatch] = useReducer(reducer, {
|
||||||
activeRef: null,
|
activeRef: null,
|
||||||
refs: [],
|
refs: [],
|
||||||
|
@ -137,53 +137,43 @@ export const RovingTabIndexProvider = ({children, handleHomeEnd}) => {
|
||||||
|
|
||||||
const context = useMemo(() => ({state, dispatch}), [state]);
|
const context = useMemo(() => ({state, dispatch}), [state]);
|
||||||
|
|
||||||
if (handleHomeEnd) {
|
const onKeyDownHandler = useCallback((ev) => {
|
||||||
return <RovingTabIndexContext.Provider value={context}>
|
let handled = false;
|
||||||
<HomeEndHelper>
|
if (handleHomeEnd) {
|
||||||
{ children }
|
// check if we actually have any items
|
||||||
</HomeEndHelper>
|
switch (ev.key) {
|
||||||
</RovingTabIndexContext.Provider>;
|
case Key.HOME:
|
||||||
}
|
handled = true;
|
||||||
|
// move focus to first item
|
||||||
return <RovingTabIndexContext.Provider value={context}>
|
if (context.state.refs.length > 0) {
|
||||||
{ children }
|
context.state.refs[0].current.focus();
|
||||||
</RovingTabIndexContext.Provider>;
|
}
|
||||||
};
|
break;
|
||||||
RovingTabIndexProvider.propTypes = {
|
case Key.END:
|
||||||
handleHomeEnd: PropTypes.bool,
|
handled = true;
|
||||||
};
|
// move focus to last item
|
||||||
|
if (context.state.refs.length > 0) {
|
||||||
// Helper to handle Home/End to jump to first/last roving-tab-index for widgets such as treeview
|
context.state.refs[context.state.refs.length - 1].current.focus();
|
||||||
export const HomeEndHelper = ({children}) => {
|
}
|
||||||
const context = useContext(RovingTabIndexContext);
|
break;
|
||||||
|
}
|
||||||
const onKeyDown = useCallback((ev) => {
|
|
||||||
// check if we actually have any items
|
|
||||||
if (context.state.refs.length <= 0) return;
|
|
||||||
|
|
||||||
let handled = true;
|
|
||||||
switch (ev.key) {
|
|
||||||
case Key.HOME:
|
|
||||||
// move focus to first item
|
|
||||||
context.state.refs[0].current.focus();
|
|
||||||
break;
|
|
||||||
case Key.END:
|
|
||||||
// move focus to last item
|
|
||||||
context.state.refs[context.state.refs.length - 1].current.focus();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
handled = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handled) {
|
if (handled) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
|
} else if (onKeyDown) {
|
||||||
|
return onKeyDown(ev);
|
||||||
}
|
}
|
||||||
}, [context.state]);
|
}, [context.state, onKeyDown, handleHomeEnd]);
|
||||||
|
|
||||||
return <div onKeyDown={onKeyDown}>
|
return <RovingTabIndexContext.Provider value={context}>
|
||||||
{ children }
|
{ children({onKeyDownHandler}) }
|
||||||
</div>;
|
</RovingTabIndexContext.Provider>;
|
||||||
|
};
|
||||||
|
RovingTabIndexProvider.propTypes = {
|
||||||
|
handleHomeEnd: PropTypes.bool,
|
||||||
|
onKeyDown: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Hook to register a roving tab index
|
// Hook to register a roving tab index
|
||||||
|
|
|
@ -142,10 +142,6 @@ export default class RoomSubList extends React.PureComponent {
|
||||||
|
|
||||||
onHeaderKeyDown = (ev) => {
|
onHeaderKeyDown = (ev) => {
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
case Key.TAB:
|
|
||||||
// Prevent LeftPanel handling Tab if focus is on the sublist header itself
|
|
||||||
ev.stopPropagation();
|
|
||||||
break;
|
|
||||||
case Key.ARROW_LEFT:
|
case Key.ARROW_LEFT:
|
||||||
// On ARROW_LEFT collapse the room sublist
|
// On ARROW_LEFT collapse the room sublist
|
||||||
if (!this.state.hidden && !this.props.forceExpand) {
|
if (!this.state.hidden && !this.props.forceExpand) {
|
||||||
|
|
|
@ -128,7 +128,8 @@ export default createReactClass({
|
||||||
'mx_RoomTile_badgeShown': this.state.badgeHover || isMenuDisplayed,
|
'mx_RoomTile_badgeShown': this.state.badgeHover || isMenuDisplayed,
|
||||||
});
|
});
|
||||||
|
|
||||||
const label = <div title={this.props.group.groupId} className={nameClasses} dir="auto">
|
// XXX: this is a workaround for Firefox giving this div a tabstop :( [tabIndex]
|
||||||
|
const label = <div title={this.props.group.groupId} className={nameClasses} tabIndex={-1} dir="auto">
|
||||||
{ groupName }
|
{ groupName }
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
|
|
|
@ -777,21 +777,22 @@ export default createReactClass({
|
||||||
|
|
||||||
const subListComponents = this._mapSubListProps(subLists);
|
const subListComponents = this._mapSubListProps(subLists);
|
||||||
|
|
||||||
const {resizeNotifier, collapsed, searchFilter, ConferenceHandler, ...props} = this.props; // eslint-disable-line
|
const {resizeNotifier, collapsed, searchFilter, ConferenceHandler, onKeyDown, ...props} = this.props; // eslint-disable-line
|
||||||
return (
|
return (
|
||||||
<div
|
<RovingTabIndexProvider handleHomeEnd={true} onKeyDown={onKeyDown}>
|
||||||
{...props}
|
{({onKeyDownHandler}) => <div
|
||||||
ref={this._collectResizeContainer}
|
{...props}
|
||||||
className="mx_RoomList"
|
onKeyDown={onKeyDownHandler}
|
||||||
role="tree"
|
ref={this._collectResizeContainer}
|
||||||
aria-label={_t("Rooms")}
|
className="mx_RoomList"
|
||||||
onMouseMove={this.onMouseMove}
|
role="tree"
|
||||||
onMouseLeave={this.onMouseLeave}
|
aria-label={_t("Rooms")}
|
||||||
>
|
onMouseMove={this.onMouseMove}
|
||||||
<RovingTabIndexProvider handleHomeEnd={true}>
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
>
|
||||||
{ subListComponents }
|
{ subListComponents }
|
||||||
</RovingTabIndexProvider>
|
</div> }
|
||||||
</div>
|
</RovingTabIndexProvider>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -353,7 +353,8 @@ export default createReactClass({
|
||||||
});
|
});
|
||||||
|
|
||||||
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
|
subtextLabel = subtext ? <span className="mx_RoomTile_subtext">{ subtext }</span> : null;
|
||||||
label = <div title={name} className={nameClasses} dir="auto">{ name }</div>;
|
// XXX: this is a workaround for Firefox giving this div a tabstop :( [tabIndex]
|
||||||
|
label = <div title={name} className={nameClasses} tabIndex={-1} dir="auto">{ name }</div>;
|
||||||
} else if (this.state.hover) {
|
} else if (this.state.hover) {
|
||||||
const Tooltip = sdk.getComponent("elements.Tooltip");
|
const Tooltip = sdk.getComponent("elements.Tooltip");
|
||||||
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
|
tooltip = <Tooltip className="mx_RoomTile_tooltip" label={this.props.room.name} dir="auto" />;
|
||||||
|
|
|
@ -47,7 +47,7 @@ const button4 = <Button key={4}>d</Button>;
|
||||||
describe("RovingTabIndex", () => {
|
describe("RovingTabIndex", () => {
|
||||||
it("RovingTabIndexProvider renders children as expected", () => {
|
it("RovingTabIndexProvider renders children as expected", () => {
|
||||||
const wrapper = mount(<RovingTabIndexProvider>
|
const wrapper = mount(<RovingTabIndexProvider>
|
||||||
<div><span>Test</span></div>
|
{() => <div><span>Test</span></div>}
|
||||||
</RovingTabIndexProvider>);
|
</RovingTabIndexProvider>);
|
||||||
expect(wrapper.text()).toBe("Test");
|
expect(wrapper.text()).toBe("Test");
|
||||||
expect(wrapper.html()).toBe('<div><span>Test</span></div>');
|
expect(wrapper.html()).toBe('<div><span>Test</span></div>');
|
||||||
|
@ -55,9 +55,11 @@ describe("RovingTabIndex", () => {
|
||||||
|
|
||||||
it("RovingTabIndexProvider works as expected with useRovingTabIndex", () => {
|
it("RovingTabIndexProvider works as expected with useRovingTabIndex", () => {
|
||||||
const wrapper = mount(<RovingTabIndexProvider>
|
const wrapper = mount(<RovingTabIndexProvider>
|
||||||
{ button1 }
|
{() => <React.Fragment>
|
||||||
{ button2 }
|
{ button1 }
|
||||||
{ button3 }
|
{ button2 }
|
||||||
|
{ button3 }
|
||||||
|
</React.Fragment>}
|
||||||
</RovingTabIndexProvider>);
|
</RovingTabIndexProvider>);
|
||||||
|
|
||||||
// should begin with 0th being active
|
// should begin with 0th being active
|
||||||
|
@ -80,14 +82,14 @@ describe("RovingTabIndex", () => {
|
||||||
|
|
||||||
// update the children, it should remain on the same button
|
// update the children, it should remain on the same button
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
children: [button1, button4, button2, button3],
|
children: () => [button1, button4, button2, button3],
|
||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
checkTabIndexes(wrapper.find("button"), [-1, -1, 0, -1]);
|
checkTabIndexes(wrapper.find("button"), [-1, -1, 0, -1]);
|
||||||
|
|
||||||
// update the children, remove the active button, it should move to the next one
|
// update the children, remove the active button, it should move to the next one
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
children: [button1, button4, button3],
|
children: () => [button1, button4, button3],
|
||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
checkTabIndexes(wrapper.find("button"), [-1, -1, 0]);
|
checkTabIndexes(wrapper.find("button"), [-1, -1, 0]);
|
||||||
|
@ -95,13 +97,15 @@ describe("RovingTabIndex", () => {
|
||||||
|
|
||||||
it("RovingTabIndexProvider works as expected with RovingTabIndexWrapper", () => {
|
it("RovingTabIndexProvider works as expected with RovingTabIndexWrapper", () => {
|
||||||
const wrapper = mount(<RovingTabIndexProvider>
|
const wrapper = mount(<RovingTabIndexProvider>
|
||||||
{ button1 }
|
{() => <React.Fragment>
|
||||||
{ button2 }
|
{ button1 }
|
||||||
<RovingTabIndexWrapper>
|
{ button2 }
|
||||||
{({onFocus, isActive, ref}) =>
|
<RovingTabIndexWrapper>
|
||||||
<button onFocus={onFocus} tabIndex={isActive ? 0 : -1} ref={ref}>.</button>
|
{({onFocus, isActive, ref}) =>
|
||||||
}
|
<button onFocus={onFocus} tabIndex={isActive ? 0 : -1} ref={ref}>.</button>
|
||||||
</RovingTabIndexWrapper>
|
}
|
||||||
|
</RovingTabIndexWrapper>
|
||||||
|
</React.Fragment>}
|
||||||
</RovingTabIndexProvider>);
|
</RovingTabIndexProvider>);
|
||||||
|
|
||||||
// should begin with 0th being active
|
// should begin with 0th being active
|
||||||
|
|
Loading…
Reference in a new issue