Skip to content

Commit f935f4d

Browse files
authored
Merge pull request #15 from ryami333/hotfix/ipad-fix
Ipad touch-away handling
2 parents ccd2a2f + 810919d commit f935f4d

File tree

3 files changed

+147
-54
lines changed

3 files changed

+147
-54
lines changed

packages/react-accessible-tooltip/src/Tooltip.js

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,18 @@ class Tooltip extends Component<TooltipProps, TooltipState> {
5151
isHidden: true,
5252
};
5353

54-
onBlur({ relatedTarget, currentTarget }: SyntheticFocusEvent<HTMLElement>) {
54+
componentDidMount() {
55+
document.addEventListener('touchstart', this.handleTouch);
56+
}
57+
58+
componentWillUnmount() {
59+
document.removeEventListener('touchstart', this.handleTouch);
60+
}
61+
62+
onBlur = ({
63+
relatedTarget,
64+
currentTarget,
65+
}: SyntheticFocusEvent<HTMLElement>) => {
5566
// relatedTarget is better for React testability etc, but activeElement works as an IE11 fallback:
5667
const newTarget = relatedTarget || document.activeElement;
5768

@@ -61,20 +72,37 @@ class Tooltip extends Component<TooltipProps, TooltipState> {
6172
} else if (!currentTarget.contains(newTarget)) {
6273
this.hide();
6374
}
64-
}
75+
};
76+
77+
// This handles the support for touch devices that do not trigger blur on 'touch-away'.
78+
handleTouch = ({ target }: Event) => {
79+
const { activeElement } = document;
80+
81+
if (!(activeElement instanceof Element) || !(target instanceof Element))
82+
return;
83+
84+
if (
85+
this.container instanceof Element &&
86+
!this.container.contains(target) && // touch target not a tooltip descendent
87+
!this.state.isHidden // prevent redundant state change
88+
) {
89+
this.hide();
90+
}
91+
};
6592

66-
hide = (): void => {
93+
hide = () => {
6794
this.setState({ isHidden: true });
6895
};
6996

70-
show = (): void => {
97+
show = () => {
7198
this.setState({ isHidden: false });
7299
};
73100

74-
toggle = (): void => {
101+
toggle = () => {
75102
this.setState({ isHidden: !this.state.isHidden });
76103
};
77104

105+
container: ?HTMLDivElement;
78106
identifier: string;
79107

80108
render() {
@@ -115,8 +143,9 @@ class Tooltip extends Component<TooltipProps, TooltipState> {
115143
return (
116144
<div
117145
{...rest}
118-
onBlur={e => this.onBlur(e)}
146+
onBlur={this.onBlur}
119147
ref={ref => {
148+
this.container = ref;
120149
if (containerRef) {
121150
containerRef(ref);
122151
}

packages/react-accessible-tooltip/src/Tooltip.test.js

Lines changed: 110 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
import classnames from 'classnames';
44
import toJson from 'enzyme-to-json';
55
import { mount } from 'enzyme';
6+
import { Simulate } from 'react-dom/test-utils';
7+
import ReactDOM from 'react-dom';
8+
69
// $FlowFixMe
710
import React16 from 'react-16';
811
// $FlowFixMe
912
import React15 from 'react-15';
13+
1014
import { type LabelProps, type OverlayProps } from './';
1115

1216
function testReact(React, Tooltip) {
@@ -53,52 +57,112 @@ function testReact(React, Tooltip) {
5357
closeButton = wrapper.find(CloseButton);
5458
});
5559

56-
it(`${React.version} - asdmatches the previous snapshot`, () => {
57-
expect(toJson(wrapper)).toMatchSnapshot();
58-
});
59-
60-
it("increments the overlay's `id` attribute with each instance.", () => {
61-
expect(overlay.find('div').prop('id')).toEqual(
62-
'react-accessible-tooltip-1', // as opposed to `react-accessible-tooltip-0`
63-
);
64-
});
65-
66-
it('hides the overlay by default', () => {
67-
expect(wrapper.state('isHidden')).toBeTruthy();
68-
});
69-
70-
it('reveals the overlay when the label is focussed, and hides the overlay when the whole tooltip is blurred.', () => {
71-
label.simulate('focus');
72-
expect(wrapper.state('isHidden')).toBeFalsy();
73-
74-
overlay.simulate('focus');
75-
expect(wrapper.state('isHidden')).toBeFalsy();
76-
77-
label.simulate('blur');
78-
expect(wrapper.state('isHidden')).toBeTruthy();
79-
});
80-
81-
it('respects a manual close request', () => {
82-
label.simulate('focus');
83-
expect(wrapper.state('isHidden')).toBeFalsy();
84-
85-
closeButton.simulate('click');
86-
expect(wrapper.state('isHidden')).toBeTruthy();
87-
});
88-
89-
it('respects the containerRef prop', () => {
90-
const containerRef = jest.fn();
91-
92-
wrapper = mount(
93-
<Tooltip
94-
label={Label}
95-
overlay={Overlay}
96-
containerRef={containerRef}
97-
/>,
98-
);
99-
100-
expect(containerRef.mock.calls.length).toEqual(1);
101-
expect(containerRef.mock.calls[0][0]).toBeInstanceOf(HTMLDivElement);
60+
describe(`${React.version} -`, () => {
61+
it('matches the previous snapshot', () => {
62+
expect(toJson(wrapper)).toMatchSnapshot();
63+
});
64+
65+
it("increments the overlay's `id` attribute with each instance.", () => {
66+
expect(overlay.find('div').prop('id')).toEqual(
67+
'react-accessible-tooltip-1', // as opposed to `react-accessible-tooltip-0`
68+
);
69+
});
70+
71+
it('hides the overlay by default', () => {
72+
expect(wrapper.state('isHidden')).toBeTruthy();
73+
});
74+
75+
it('reveals the overlay when the label is focussed, and hides the overlay when the whole tooltip is blurred.', () => {
76+
label.simulate('focus');
77+
expect(wrapper.state('isHidden')).toBeFalsy();
78+
79+
overlay.simulate('focus');
80+
expect(wrapper.state('isHidden')).toBeFalsy();
81+
82+
label.simulate('blur');
83+
expect(wrapper.state('isHidden')).toBeTruthy();
84+
});
85+
86+
it('respects a manual close request', () => {
87+
label.simulate('focus');
88+
expect(wrapper.state('isHidden')).toBeFalsy();
89+
90+
closeButton.simulate('click');
91+
expect(wrapper.state('isHidden')).toBeTruthy();
92+
});
93+
94+
it('respects the containerRef prop', () => {
95+
const containerRef = jest.fn();
96+
97+
wrapper = mount(
98+
<Tooltip
99+
label={Label}
100+
overlay={Overlay}
101+
containerRef={containerRef}
102+
/>,
103+
);
104+
105+
wrapper.simulate('touchStart', {
106+
type: 'touchstart',
107+
x: 0,
108+
y: 0,
109+
});
110+
111+
expect(containerRef.mock.calls.length).toEqual(1);
112+
expect(containerRef.mock.calls[0][0]).toBeInstanceOf(
113+
HTMLDivElement,
114+
);
115+
});
116+
117+
describe('touch devices -', () => {
118+
// let containerRef;
119+
let labelRef;
120+
let overlayRef;
121+
122+
beforeAll(() => {
123+
const testRoot = document.createElement('div');
124+
ReactDOM.render(
125+
<div
126+
// ref={_containerRef => {
127+
// containerRef = _containerRef;
128+
// }}
129+
>
130+
<Tooltip
131+
label={({ labelAttributes }) => (
132+
<div
133+
{...labelAttributes}
134+
ref={_labelRef => {
135+
labelRef = _labelRef;
136+
}}
137+
/>
138+
)}
139+
overlay={({ overlayAttributes }) => (
140+
<div
141+
{...overlayAttributes}
142+
ref={_overlayRef => {
143+
overlayRef = _overlayRef;
144+
}}
145+
/>
146+
)}
147+
/>
148+
</div>,
149+
testRoot,
150+
);
151+
});
152+
153+
it('opens on focus', () => {
154+
expect(overlayRef.getAttribute('aria-hidden')).toEqual('true');
155+
Simulate.focus(labelRef);
156+
expect(overlayRef.getAttribute('aria-hidden')).toEqual('false');
157+
});
158+
159+
it('closes on touch-away', () => {
160+
const testEvent = new Event('touchstart', { bubbles: true });
161+
// $FlowFixMe
162+
document.body.dispatchEvent(testEvent);
163+
expect(overlayRef.getAttribute('aria-hidden')).toEqual('true');
164+
});
165+
});
102166
});
103167
}
104168

packages/react-accessible-tooltip/src/__snapshots__/Tooltip.test.js.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`<Tooltip /> React 15 15.6.1 - asdmatches the previous snapshot 1`] = `
3+
exports[`<Tooltip /> React 15 15.6.1 - matches the previous snapshot 1`] = `
44
<Tooltip
55
label={[Function]}
66
overlay={[Function]}
@@ -64,7 +64,7 @@ exports[`<Tooltip /> React 15 15.6.1 - asdmatches the previous snapshot 1`] = `
6464
</Tooltip>
6565
`;
6666

67-
exports[`<Tooltip /> React 16 16.2.0 - asdmatches the previous snapshot 1`] = `
67+
exports[`<Tooltip /> React 16 16.2.0 - matches the previous snapshot 1`] = `
6868
<Tooltip
6969
label={[Function]}
7070
overlay={[Function]}

0 commit comments

Comments
 (0)