Skip to content

Commit

Permalink
fix: support old refs
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Mar 11, 2019
1 parent 024ed7f commit 8b87c2f
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 27 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"mocha": true,
},
"rules": {
"no-cond-assign": "off",
"jsx-a11y/href-no-hash": "off",
// Override airbnb to disable devDependencies check until we figure out how to exclude tests files
"import/no-extraneous-dependencies": [2, {"optionalDependencies": false}],
Expand All @@ -24,4 +25,4 @@
// Turn off https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-named-as-default-member.md
"import/no-named-as-default-member": 0,
}
}
}
58 changes: 58 additions & 0 deletions _tests/FocusLock.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,64 @@ describe('react-focus-lock', () => {
}, 1);
});

it('Should handle focus groups - shard', (done) => {
const ref1 = React.createRef();
const ref2 = React.createRef();
const TestCase = () => (
<div>
<div>
text
<button className="action1">action1</button>
text
</div>
<FocusLock shards={[ref1, ref2.current, null]} autoFocus>
<button id="b1">button1</button>
<button id="b2">button2</button>
</FocusLock>
<button id="b3" ref={ref1}>button3</button>
<button id="b5">button5</button>
<button id="b4" ref={ref2}>button4</button>
<div>
text
<button className="action3">action1</button>
text
</div>
</div>
);

const wrapper = mount(<TestCase />, mountPoint);
wrapper.update().find('#b2').getDOMNode().focus();
// update wrapper to propagate ref2
wrapper.setProps({});
expect(document.activeElement.innerHTML).to.be.equal('button2');
setTimeout(() => {
expect(document.activeElement.innerHTML).to.be.equal('button2');
wrapper.find('#b3').simulate('focus');
wrapper.find('#b3').getDOMNode().focus();
wrapper.find('#b1').simulate('blur');
expect(document.activeElement.innerHTML).to.be.equal('button3');
setTimeout(() => {
expect(document.activeElement.innerHTML).to.be.equal('button3');
wrapper.find('#b4').simulate('focus');
wrapper.find('#b4').getDOMNode().focus();
wrapper.find('#b1').simulate('blur');
expect(document.activeElement.innerHTML).to.be.equal('button4');
setTimeout(() => {
expect(document.activeElement.innerHTML).to.be.equal('button4');
wrapper.find('#b5').simulate('focus');
wrapper.find('#b5').getDOMNode().focus();
expect(document.activeElement.innerHTML).to.be.equal('button5');
wrapper.find('#b1').simulate('blur');
setTimeout(() => {
// it should be 3 :(
expect(document.activeElement.innerHTML).to.be.equal('button3');
done();
}, 1);
}, 1);
}, 1);
}, 1);
});

it('Should handle focus groups - disabled', (done) => {
const wrapper = mount(
<div>
Expand Down
2 changes: 1 addition & 1 deletion react-focus-lock.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ declare module 'react-focus-lock' {
/**
* Shards forms a scattered lock, same as `group` does, but in more "low" and controlled way
*/
shards?: Array<React.RefObject<any>>;
shards?: Array<React.RefObject<any> | HTMLElement>;

children: React.ReactNode;
}
Expand Down
10 changes: 7 additions & 3 deletions src/FocusGuard.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ export const hiddenGuard = {
left: '1px',
};

const InFocusGuard = ({children}) => (
const InFocusGuard = ({ children }) => (
<React.Fragment>
<div key="guard-first" data-focus-guard data-focus-auto-guard style={hiddenGuard}/>
<div key="guard-first" data-focus-guard data-focus-auto-guard style={hiddenGuard} />
{children}
{children && <div key="guard-last" data-focus-guard data-focus-auto-guard style={hiddenGuard}/>}
{children && <div key="guard-last" data-focus-guard data-focus-auto-guard style={hiddenGuard} />}
</React.Fragment>
);
InFocusGuard.propTypes = {
children: PropTypes.node,
};

InFocusGuard.defaultProps = {
children: null,
};


export default InFocusGuard;
21 changes: 11 additions & 10 deletions src/Lock.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, {Component} from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {constants} from 'focus-lock';
import FocusTrap, {onBlur, onFocus} from './Trap';
import {hiddenGuard} from './FocusGuard';
import { constants } from 'focus-lock';
import FocusTrap, { onBlur, onFocus } from './Trap';
import { hiddenGuard } from './FocusGuard';

const RenderChildren = ({children}) => <div>{children}</div>;
const RenderChildren = ({ children }) => <div>{children}</div>;
RenderChildren.propTypes = {
children: PropTypes.node.isRequired,
};
Expand Down Expand Up @@ -81,7 +81,7 @@ class FocusLock extends Component {
as: Container = 'div',
lockProps: containerProps = {},
} = this.props;
const {observed} = this.state;
const { observed } = this.state;

if (process.env.NODE_ENV !== 'production') {
if (typeof allowTextSelection !== 'undefined') {
Expand All @@ -99,8 +99,8 @@ class FocusLock extends Component {
return (
<Fragment>
{!noFocusGuards && [
<div key="guard-first" data-focus-guard tabIndex={disabled ? -1 : 0} style={hiddenGuard}/>, // nearest focus guard
<div key="guard-nearest" data-focus-guard tabIndex={disabled ? -1 : 1} style={hiddenGuard}/>, // first tabbed element guard
<div key="guard-first" data-focus-guard tabIndex={disabled ? -1 : 0} style={hiddenGuard} />, // nearest focus guard
<div key="guard-nearest" data-focus-guard tabIndex={disabled ? -1 : 1} style={hiddenGuard} />, // first tabbed element guard
]}
<Container
ref={this.setObserveNode}
Expand All @@ -123,7 +123,7 @@ class FocusLock extends Component {
</Container>
{
!noFocusGuards &&
<div data-focus-guard tabIndex={disabled ? -1 : 0} style={hiddenGuard}/>
<div data-focus-guard tabIndex={disabled ? -1 : 0} style={hiddenGuard} />
}
</Fragment>
);
Expand All @@ -144,7 +144,7 @@ FocusLock.propTypes = {
className: PropTypes.string,

whiteList: PropTypes.func,
shards: PropTypes.arrayOf(PropTypes.shape({current: PropTypes.instanceOf(Element)})),
shards: PropTypes.arrayOf(PropTypes.shape({ current: PropTypes.instanceOf(Element) })),

as: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]),
lockProps: PropTypes.object,
Expand All @@ -166,6 +166,7 @@ FocusLock.defaultProps = {
shards: undefined,
as: 'div',
lockProps: {},

onActivation: undefined,
onDeactivation: undefined,
};
Expand Down
26 changes: 15 additions & 11 deletions src/Trap.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import withSideEffect from 'react-clientside-effect';
import moveFocusInside, {focusInside, focusIsHidden, getFocusabledIn} from 'focus-lock';
import {deferAction} from './util';
import moveFocusInside, { focusInside, focusIsHidden, getFocusabledIn } from 'focus-lock';
import { deferAction } from './util';

const focusOnBody = () => (
document && document.activeElement === document.body
Expand All @@ -22,7 +22,7 @@ const focusWhitelisted = activeElement => (
);

const recordPortal = (observerNode, portaledElement) => {
lastPortaledElement = {observerNode, portaledElement};
lastPortaledElement = { observerNode, portaledElement };
};

const focusIsPortaledPair = element => (
Expand All @@ -47,14 +47,19 @@ function autoGuard(startIndex, end, step, allNodes) {
}
}

const extractRef = ref => ((ref && 'current' in ref) ? ref.current : ref);

const activateTrap = () => {
let result = false;
if (lastActiveTrap) {
const {observed, persistentFocus, autoFocus, shards} = lastActiveTrap;
const { observed, persistentFocus, autoFocus, shards } = lastActiveTrap;
const workingNode = observed || (lastPortaledElement && lastPortaledElement.portaledElement);
const activeElement = document && document.activeElement;
if (workingNode) {
const workingArea = [workingNode, ...shards.map(({current}) => current)];
const workingArea = [
workingNode,
...shards.map(extractRef).filter(Boolean),
];

if (!activeElement || focusWhitelisted(activeElement)) {
if (persistentFocus || !isFreeFocus() || (!lastActiveFocus && autoFocus)) {
Expand All @@ -80,12 +85,12 @@ const activateTrap = () => {
if (document) {
const newActiveElement = document && document.activeElement;
const allNodes = getFocusabledIn(workingArea);
const focusedItem = allNodes.find(({node}) => node === newActiveElement);
const focusedItem = allNodes.find(({ node }) => node === newActiveElement);
if (focusedItem) {
// remove old focus
allNodes
.filter(({guard, node}) => guard && node.dataset.focusAutoGuard)
.forEach(({node}) => node.removeAttribute('tabIndex'));
.filter(({ guard, node }) => guard && node.dataset.focusAutoGuard)
.forEach(({ node }) => node.removeAttribute('tabIndex'));

const focusedIndex = allNodes.indexOf(focusedItem);
autoGuard(focusedIndex, allNodes.length, +1, allNodes);
Expand Down Expand Up @@ -120,7 +125,7 @@ export const onFocus = (event) => {

const FocusWatcher = () => null;

const FocusTrap = ({children}) => (
const FocusTrap = ({ children }) => (
<div onBlur={onBlur} onFocus={onFocus}>
{children}
</div>
Expand All @@ -140,10 +145,9 @@ const detachHandler = () => {
document.removeEventListener('focusout', onBlur);
};


function reducePropsToState(propsList) {
return propsList
.filter(({disabled}) => !disabled)
.filter(({ disabled }) => !disabled)
.slice(-1)[0];
}

Expand Down
2 changes: 1 addition & 1 deletion stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {PortalCase, ShardPortalCase} from './Portal';
import {MUISelect, MUISelectWhite} from './MUI';
import Fight from './FocusFighting';
import {StyledComponent, StyledSection} from "./Custom";
import {DisabledForm, DisabledFormWithTabIndex} from "./Disabled";
import {AutoDisabledForm, DisabledForm, DisabledFormWithTabIndex} from "./Disabled";

const frameStyle = {
width: '400px',
Expand Down

0 comments on commit 8b87c2f

Please sign in to comment.