diff --git a/addon-test-support/helpers/element.js b/addon-test-support/helpers/element.js index 7a8cf8729..c42fa69dd 100644 --- a/addon-test-support/helpers/element.js +++ b/addon-test-support/helpers/element.js @@ -1,9 +1,3 @@ -export function getScale(element) { - let rect = element.getBoundingClientRect(); +import { getScale } from 'ember-table/-private/utils/element'; - if (element.offsetHeight === rect.height || rect.height === 0) { - return 1; - } else { - return element.offsetHeight / rect.height; - } -} +export { getScale }; diff --git a/addon-test-support/pages/ember-table.js b/addon-test-support/pages/ember-table.js index d90898f1a..a9be52c58 100644 --- a/addon-test-support/pages/ember-table.js +++ b/addon-test-support/pages/ember-table.js @@ -60,6 +60,20 @@ export default PageObject.extend({ return findElement(this, 'table').offsetWidth; }, + /** + * Retrieves the logical width of the table. + */ + get logicalWidth() { + return window.getComputedStyle(findElement(this, 'table')).width; + }, + + /** + * Retrieves the rendered width of the table. + */ + get renderedWidth() { + return findElement(this, 'table').getBoundingClientRect().width; + }, + /** * Returns the table container width. * @@ -72,6 +86,20 @@ export default PageObject.extend({ return findElement(this).offsetWidth; }, + /** + * Retrieves the logical width of the container. + */ + get logicalContainerWidth() { + return window.getComputedStyle(findElement(this)).width; + }, + + /** + * Retrieves the rendered width of the container. + */ + get renderedContainerWidth() { + return findElement(this).getBoundingClientRect().width; + }, + /** * Returns the specified scroll indicator element */ diff --git a/addon/-private/sticky/table-sticky-polyfill.js b/addon/-private/sticky/table-sticky-polyfill.js index 7ff1e3c56..f6784fe5f 100644 --- a/addon/-private/sticky/table-sticky-polyfill.js +++ b/addon/-private/sticky/table-sticky-polyfill.js @@ -1,3 +1,5 @@ +import { getScale } from '../utils/element'; + /* global ResizeSensor */ /* eslint-disable ember/no-observers */ @@ -171,7 +173,7 @@ class TableStickyPolyfill { */ repositionStickyElements = () => { let table = this.element.parentNode; - let scale = table.offsetHeight / table.getBoundingClientRect().height; + let scale = getScale(table); let containerHeight = table.parentNode.offsetHeight; // exclude ResizeSensor divs diff --git a/addon/-private/utils/element.js b/addon/-private/utils/element.js index cd5dce4cd..695d33318 100644 --- a/addon/-private/utils/element.js +++ b/addon/-private/utils/element.js @@ -30,13 +30,45 @@ export function closest(el, selector) { return null; } +/* + * Calculate the scale of difference between an element's logical height + * (the offsetHeight or getComputedStyle height) compared to its rendered + * height (via getBoundingClientRect). + * + * Note that there are some interesting edge cases to consider around + * these APIs. + * + * - `getComputedStyle` returns a string from styles in pixels. For + * example `48.23px`. The precision of this API in Chrome seems to + * be 3 decimal places. Additionally, there can be unexpected values + * such as `auto`. Finally, this API may be slow compared to other + * DOM API calls. + * - `offsetHeight` always returns in integer. If the height of an + * element is at float precision, then the value is rounded. This + * makes it at best useful as a fallback. + * + */ export function getScale(element) { let rect = element.getBoundingClientRect(); + let renderedHeight = rect.height; - if (element.offsetHeight === rect.height || rect.height === 0) { + if (renderedHeight === 0) { + return 1; + } + + let computedHeightStyleValue = window.getComputedStyle(element).height; + let computedHeightInPixels = Number( + computedHeightStyleValue.substring(0, computedHeightStyleValue.length - 2) + ); + + if (isNaN(computedHeightInPixels)) { + computedHeightInPixels = element.offsetHeight; + } + + if (computedHeightInPixels === renderedHeight) { return 1; } else { - return element.offsetHeight / rect.height; + return computedHeightInPixels / renderedHeight; } } diff --git a/tests/unit/-private/element-utils-test.js b/tests/unit/-private/element-utils-test.js new file mode 100644 index 000000000..9e6eee045 --- /dev/null +++ b/tests/unit/-private/element-utils-test.js @@ -0,0 +1,33 @@ +import { module, test } from 'qunit'; +import { getScale } from 'ember-table/-private/utils/element'; + +module('Unit | Private | element', function(hooks) { + hooks.beforeEach(function() { + /* + * Use an element outside the normal test harness as that harness + * uses scale() on the test container. + */ + this.element = document.createElement('div'); + document.body.append(this.element); + }); + + hooks.afterEach(function() { + this.element.remove(); + }); + + test('can get the scale of a transformed element', function(assert) { + let div = document.createElement('div'); + div.style.height = '4px'; + this.element.append(div); + + assert.equal(getScale(div), 1, 'scale on a simple element is correct'); + + div.style.transform = 'scale(0.5)'; + + assert.equal(getScale(div), 2, 'scale on a scaled element is correct'); + + div.style.height = '1.5px'; + + assert.equal(getScale(div), 2, 'scale on a scaled element is correct'); + }); +});