Skip to content

Commit

Permalink
feat(List): add focusQuery and focusFirstQuery props
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Biggs authored and daanishnasir committed Jun 18, 2019
1 parent a669735 commit 2105ac7
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ ShallowWrapper {
],
"className": "",
"focusFirst": true,
"focusFirstQuery": "",
"focusQuery": "",
"id": null,
"itemRole": "listitem",
"onSelect": [Function],
Expand Down Expand Up @@ -150,6 +152,8 @@ ShallowWrapper {
],
"className": "",
"focusFirst": true,
"focusFirstQuery": "",
"focusQuery": "",
"id": null,
"itemRole": "listitem",
"onSelect": [Function],
Expand Down
49 changes: 34 additions & 15 deletions react/src/lib/List/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ class List extends React.Component {
}

determineInitialFocus = () => {
const items = qsa(this.listNode, `.md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)`);
const { focusFirstQuery } = this.props;
const items = qsa(this.listNode, focusFirstQuery || `.md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)`);

this._needsRefocus = true;
items.length && this.getNextFocusedChild(items[0], 0);
items.length && this.getNextFocusedChild(items, items[0], 0);
}

getIncludesFirstCharacter = (str, char) =>
Expand All @@ -72,12 +73,11 @@ class List extends React.Component {
.toLowerCase()
.includes(char);

getNextFocusedChild(current, offset) {
getNextFocusedChild(items, current, offset) {
if (!this.listNode) return null;
const { shouldLoop } = this.props;
const { listContext } = this.state;

const items = qsa(this.listNode, `.md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)`);
const possibleIndex = items.indexOf(current) + offset;

const getIndex = () => {
Expand All @@ -104,15 +104,27 @@ class List extends React.Component {
getValue = (arr, index, attribute) => (
arr[index].attributes[`data-md-${attribute}-key`]
&& arr[index].attributes[`data-md-${attribute}-key`].value
);
)

getFocusableItems = () => {
if (!this.listNode) return null;
const { focusQuery } = this.props;

const defaultItems = qsa(this.listNode, `.md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)`);
const customItems = focusQuery && qsa(this.listNode, focusQuery) || [];

return customItems.length
? customItems.filter(item => customItems.indexOf(item) >= 0)
: defaultItems;
}

handleKeyDown = e => {
const { shouldFocusActive } = this.props;
const { focus } = this.state.listContext;
let clickEvent;
const tgt = e.currentTarget;
const char = e.key;
const items = qsa(this.listNode, `.md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)`);
const items = this.getFocusableItems();
const length = items.length && items.length - 1 || 0;
const focusIdx = focus && items.indexOf(this.listNode.querySelector(`[data-md-event-key="${focus}"]`)) || 0;
let flag = false;
Expand Down Expand Up @@ -149,14 +161,14 @@ class List extends React.Component {

case 38:
case 37:
this.getNextFocusedChild(tgt, -1);
this.getNextFocusedChild(items, tgt, -1);
this._needsRefocus = true;
flag = true;
break;

case 39:
case 40:
this.getNextFocusedChild(tgt, 1);
this.getNextFocusedChild(items, tgt, 1);
this._needsRefocus = true;
flag = true;
break;
Expand Down Expand Up @@ -193,10 +205,11 @@ class List extends React.Component {
const { onSelect, trackActive } = this.props;
const { active } = this.state.listContext;
const { eventKey, label, value } = opts;
const items = qsa(this.listNode, '.md-list-item');

const items = this.getFocusableItems();
const index = items.indexOf(this.listNode.querySelector(`[data-md-event-key="${eventKey}"]`));

this.setFocus(index);
this.setFocus(items, index);
// Don't do anything if onSelect Event Handler is present
if (onSelect) {
return onSelect(e, {
Expand All @@ -223,18 +236,16 @@ class List extends React.Component {
active: this.getValue(items, index, 'event')
}
}));
};

setFocus = index => {
const items = qsa(this.listNode, '.md-list-item');
}

setFocus = (items, index) => {
this.setState(state => ({
listContext: {
...state.listContext,
focus: this.getValue(items, index, 'event'),
}
}));
};
}

setFocusByFirstCharacter = (char, focusIdx, items, length) => {
const { listContext } = this.state;
Expand Down Expand Up @@ -307,6 +318,8 @@ class List extends React.Component {

const otherProps = omit({...props}, [
'focusFirst',
'focusFirstQuery',
'focusQuery',
'itemRole',
'shouldFocusActive',
'shouldLoop',
Expand Down Expand Up @@ -363,6 +376,10 @@ List.propTypes = {
className: PropTypes.string,
/** @prop Sets first List item to have focus | true */
focusFirst: PropTypes.bool,
/** @prop Queries children to find matching item to have focus | '' */
focusFirstQuery: PropTypes.string,
/** @prop Additional elements that can be focused by selector | '' */
focusQuery: PropTypes.string,
/** @prop Optional ID value of List | null */
id: PropTypes.string,
/** @prop Optional tabType prop type to manually set child role | 'listitem' */
Expand Down Expand Up @@ -392,6 +409,8 @@ List.defaultProps = {
id: null,
itemRole: 'listitem',
focusFirst: true,
focusFirstQuery: '',
focusQuery: '',
onSelect: null,
role: 'list',
shouldFocusActive: false,
Expand Down
2 changes: 2 additions & 0 deletions react/src/lib/List/tests/__snapshots__/index.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ ShallowWrapper {
active={null}
className=""
focusFirst={true}
focusFirstQuery=""
focusQuery=""
id="test"
itemRole="listitem"
onSelect={null}
Expand Down
13 changes: 12 additions & 1 deletion react/src/lib/SidebarNav/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class SidebarNav extends React.PureComponent {
const {
children,
className,
focusQuery,
headerNode,
title,
...props
Expand All @@ -36,7 +37,14 @@ class SidebarNav extends React.PureComponent {
' md-sidebar-nav__group--primary' +
`${(className && ` ${className}`) || ''}`
}
{...props}>
focusQuery={`
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
${focusQuery ? `, ${focusQuery}` : ''}
`}
{...props}
>
{children}
</List>
</div>
Expand All @@ -53,6 +61,8 @@ SidebarNav.propTypes = {
children: PropTypes.node,
/** @prop Optional css class string | '' */
className: PropTypes.string,
/** @prop Additional elements that can be focused by selector | '' */
focusQuery: PropTypes.string,
/** @prop Optional header node to replace header element | '' */
headerNode: PropTypes.node,
/** @prop Optional string to be used for Section Title | '' */
Expand All @@ -62,6 +72,7 @@ SidebarNav.propTypes = {
SidebarNav.defaultProps = {
children: null,
className: '',
focusQuery: '',
headerNode: null,
title: ''
};
Expand Down
57 changes: 57 additions & 0 deletions react/src/lib/SidebarNav/tests/__snapshots__/index.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ShallowWrapper {
Symbol(enzyme.__root__): [Circular],
Symbol(enzyme.__unrendered__): <SidebarNav
className=""
focusQuery=""
headerNode={null}
navSectionTitle="Overview"
title=""
Expand Down Expand Up @@ -37,6 +38,13 @@ ShallowWrapper {
active={null}
className="md-sidebar-nav__group md-sidebar-nav__group--primary"
focusFirst={true}
focusFirstQuery=""
focusQuery="
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
"
id={null}
itemRole="listitem"
navSectionTitle="Overview"
Expand Down Expand Up @@ -65,6 +73,13 @@ ShallowWrapper {
active={null}
className="md-sidebar-nav__group md-sidebar-nav__group--primary"
focusFirst={true}
focusFirstQuery=""
focusQuery="
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
"
id={null}
itemRole="listitem"
navSectionTitle="Overview"
Expand Down Expand Up @@ -94,6 +109,13 @@ ShallowWrapper {
active={null}
className="md-sidebar-nav__group md-sidebar-nav__group--primary"
focusFirst={true}
focusFirstQuery=""
focusQuery="
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
"
id={null}
itemRole="listitem"
navSectionTitle="Overview"
Expand Down Expand Up @@ -121,6 +143,13 @@ ShallowWrapper {
"children": null,
"className": "md-sidebar-nav__group md-sidebar-nav__group--primary",
"focusFirst": true,
"focusFirstQuery": "",
"focusQuery": "
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
",
"id": null,
"itemRole": "listitem",
"navSectionTitle": "Overview",
Expand Down Expand Up @@ -181,6 +210,13 @@ ShallowWrapper {
active={null}
className="md-sidebar-nav__group md-sidebar-nav__group--primary"
focusFirst={true}
focusFirstQuery=""
focusQuery="
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
"
id={null}
itemRole="listitem"
navSectionTitle="Overview"
Expand Down Expand Up @@ -209,6 +245,13 @@ ShallowWrapper {
active={null}
className="md-sidebar-nav__group md-sidebar-nav__group--primary"
focusFirst={true}
focusFirstQuery=""
focusQuery="
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
"
id={null}
itemRole="listitem"
navSectionTitle="Overview"
Expand Down Expand Up @@ -238,6 +281,13 @@ ShallowWrapper {
active={null}
className="md-sidebar-nav__group md-sidebar-nav__group--primary"
focusFirst={true}
focusFirstQuery=""
focusQuery="
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
"
id={null}
itemRole="listitem"
navSectionTitle="Overview"
Expand Down Expand Up @@ -265,6 +315,13 @@ ShallowWrapper {
"children": null,
"className": "md-sidebar-nav__group md-sidebar-nav__group--primary",
"focusFirst": true,
"focusFirstQuery": "",
"focusQuery": "
.md-sidebar-nav__group--primary > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only),
.md-sidebar-nav__group--secondary.md-sidebar-nav__group--expanded > .md-sidebar-nav__group--expanded > .md-list-item:not(.disabled):not(:disabled):not(.md-list-item--read-only)
",
"id": null,
"itemRole": "listitem",
"navSectionTitle": "Overview",
Expand Down
5 changes: 5 additions & 0 deletions react/src/lib/SidebarNavItem/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class SidebarNavItem extends React.Component {
className,
level,
icon,
keyboardKey,
title,
titleNode,
...props
Expand Down Expand Up @@ -95,6 +96,7 @@ class SidebarNavItem extends React.Component {
<ListItem
className={className}
id={id}
keyboardKey={keyboardKey || title}
onClick={() => {children ? this.handleNavToggle() : false;}}
{...otherProps}
>
Expand Down Expand Up @@ -174,6 +176,8 @@ SidebarNavItem.propTypes = {
expanded: PropTypes.bool,
/** @prop Icon string or node for the title | null */
icon: PropTypes.node,
/** @prop Unique string used for keyboard navigation | '' */
keyboardKey: PropTypes.string,
// Internal Context Use Only
level: PropTypes.string,
// Internal Context Use Only
Expand All @@ -194,6 +198,7 @@ SidebarNavItem.defaultProps = {
children: null,
expanded: false,
icon: null,
keyboardKey: '',
level: null,
primary: false,
secondary: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ ShallowWrapper {
active={null}
className=""
focusFirst={true}
focusFirstQuery=""
focusQuery=""
id={null}
itemRole="listitem"
onSelect={null}
Expand All @@ -48,6 +50,8 @@ ShallowWrapper {
"children": null,
"className": "",
"focusFirst": true,
"focusFirstQuery": "",
"focusQuery": "",
"id": null,
"itemRole": "listitem",
"onSelect": null,
Expand Down Expand Up @@ -75,6 +79,8 @@ ShallowWrapper {
active={null}
className=""
focusFirst={true}
focusFirstQuery=""
focusQuery=""
id={null}
itemRole="listitem"
onSelect={null}
Expand All @@ -98,6 +104,8 @@ ShallowWrapper {
"children": null,
"className": "",
"focusFirst": true,
"focusFirstQuery": "",
"focusQuery": "",
"id": null,
"itemRole": "listitem",
"onSelect": null,
Expand Down

0 comments on commit 2105ac7

Please sign in to comment.