Skip to content

Commit

Permalink
[Popover] Fix bad positioning on IOS devices (#4638)
Browse files Browse the repository at this point in the history
* Fix popover positioning on IOS

fixes #4499

* move getOffsetTop to ios helper

* move getOffsetTop(el) inside if isIOS block

* fix: test for isIOS helper

* use fix only in input field is focused

* make test pass
  • Loading branch information
w01fgang authored and oliviertassinari committed Dec 15, 2016
1 parent 3b97892 commit ef384ac
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 1 deletion.
7 changes: 6 additions & 1 deletion src/Popover/Popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import propTypes from '../utils/propTypes';
import Paper from '../Paper';
import throttle from 'lodash.throttle';
import PopoverAnimationDefault from './PopoverAnimationDefault';
import {isIOS, getOffsetTop} from '../utils/isIOS';

class Popover extends Component {
static propTypes = {
Expand Down Expand Up @@ -242,7 +243,11 @@ class Popover extends Component {
};

a.right = rect.right || a.left + a.width;
a.bottom = rect.bottom || a.top + a.height;
if (isIOS() && document.activeElement.tagName === 'INPUT') {
a.bottom = getOffsetTop(el) + a.height;
} else {
a.bottom = rect.bottom || a.top + a.height;
}
a.middle = a.left + ((a.right - a.left) / 2);
a.center = a.top + ((a.bottom - a.top) / 2);

Expand Down
63 changes: 63 additions & 0 deletions src/Popover/Popover.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,67 @@ describe('<Popover />', () => {
assert.strictEqual(layerWrapper.find(PopoverAnimationDefault).length, 1);
});
});

describe('IOS detection', () => {
// skip tests on PhantomJS because __defineGetter__ method seems not working
if (/PhantomJS/.test(window.navigator.userAgent)) return;
const getBoundingClientRect = () => ({
x: 10,
y: 10,
width: 10,
height: 10,
top: 10,
right: 10,
bottom: 10,
left: 10,
});
const el = {
offsetHeight: 10,
offsetWidth: 10,
offsetParent: {
offsetTop: 10,
offsetParent: null,
},
offsetTop: 10,
getBoundingClientRect,
};
/* eslint-disable max-len */
const userAgentsWithIOS = [
'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53',
'Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53',
'Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3A101a Safari/419.3',
];
const nonIOSuserAgents = [
'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)',
'Mozilla/5.0 (Linux; Android 4.4.4; Nexus 7 Build/KTU84Q) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Safari/537.36',
'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+',
];
/* eslint-enable max-len */

userAgentsWithIOS.forEach((agent) => {
it(`should use absolute positioning for ${agent}`, () => {
window.navigator.__defineGetter__('userAgent', () => agent); // eslint-disable-line no-underscore-dangle,max-len
const wrapper = mountWithContext(<Popover open={true} animated={true} />);
const result = wrapper.instance().getAnchorPosition(el);
const expected = {bottom: 30, top: 10, center: 20, left: 10, right: 10, middle: 10, height: 10, width: 10};
assert.deepEqual(result, expected);
});
});

nonIOSuserAgents.forEach((agent) => {
it(`should use normal positioning for ${agent}`, () => {
window.navigator.__defineGetter__('userAgent', () => agent); // eslint-disable-line no-underscore-dangle,max-len
const wrapper = mountWithContext(<Popover open={true} animated={true} />);
const result = wrapper.instance().getAnchorPosition(el);
const expected = {bottom: 10, top: 10, center: 10, left: 10, right: 10, middle: 10, height: 10, width: 10};
assert.deepEqual(result, expected);
});
});

after(() => {
window.navigator.__defineGetter__('userAgent', function getUserAgent() { // eslint-disable-line no-underscore-dangle,max-len
return `${this.appCodeName}/${this.appVersion}`;
});
});
});
});
20 changes: 20 additions & 0 deletions src/utils/isIOS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Returns a number of pixels from the top of the screen for given dom element.
*
* @param {object} dom element
* @returns {number} A position from the top of the screen in pixels
*/
export const getOffsetTop = (elem) => {
let yPos = elem.offsetTop;
let tempEl = elem.offsetParent;

while (tempEl != null) {
yPos += tempEl.offsetTop;
tempEl = tempEl.offsetParent;
}

return yPos;
};


export const isIOS = () => /iPad|iPhone|iPod/.test(window.navigator.userAgent) && !window.MSStream;
44 changes: 44 additions & 0 deletions src/utils/isIOS.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* eslint-env mocha */
import {isIOS} from './isIOS';
import {assert} from 'chai';

describe('IOS detection helper', () => {
// skip tests on PhantomJS because __defineGetter__ method doesn't work
if (/PhantomJS/.test(window.navigator.userAgent)) return;

/* eslint-disable max-len */
const userAgentsWithIOS = [
'Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53',
'Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53',
'Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3A101a Safari/419.3',
];
const nonIOSuserAgents = [
'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)',
'Mozilla/5.0 (Linux; Android 4.4.4; Nexus 7 Build/KTU84Q) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Safari/537.36',
'Mozilla/5.0 (BlackBerry; U; BlackBerry 9900; en) AppleWebKit/534.11+ (KHTML, like Gecko) Version/7.1.0.346 Mobile Safari/534.11+',
];
/* eslint-enable max-len */

userAgentsWithIOS.forEach((agent) => {
const input = document.createElement('INPUT');
document.body.appendChild(input);
input.focus();
it('should decect IOS', () => {
window.navigator.__defineGetter__('userAgent', () => agent); // eslint-disable-line no-underscore-dangle,max-len
assert.strictEqual(isIOS(), true);
});
});

nonIOSuserAgents.forEach((agent) => {
it('should NOT decect IOS', () => {
window.navigator.__defineGetter__('userAgent', () => agent); // eslint-disable-line no-underscore-dangle,max-len
assert.strictEqual(isIOS(), false);
});
});

after(() => {
window.navigator.__defineGetter__('userAgent', function getUserAgent() { // eslint-disable-line no-underscore-dangle,max-len
return `${this.appCodeName}/${this.appVersion}`;
});
});
});

0 comments on commit ef384ac

Please sign in to comment.