diff --git a/dev-server/documents/html/z-index.mustache b/dev-server/documents/html/z-index.mustache new file mode 100644 index 00000000000..2a4a1956618 --- /dev/null +++ b/dev-server/documents/html/z-index.mustache @@ -0,0 +1,26 @@ + + + + + Hypothesis Client Test + + + +

z-index test page

+

This has a z-index of 0

+
    +
  1. This has a z-index of 25000
  2. +
  3. This has a z-index of 30000
  4. +
  5. This has a z-index of 20000 (from parent)
  6. +
  7. This has a z-index of 20000 (from parent)
  8. +
  9. This has a z-index of 20000 (from parent)
  10. +
+

This has a z-index of 0

+ + {{{hypothesisScript}}} + + \ No newline at end of file diff --git a/dev-server/templates/index.mustache b/dev-server/templates/index.mustache index 41a652a9a3c..8702b386b4d 100644 --- a/dev-server/templates/index.mustache +++ b/dev-server/templates/index.mustache @@ -18,6 +18,7 @@
  • Poems and Songs of Robert Burns
  • The Disappearance of Lady Carfax, by Arthur Conan Doyle
  • Anna Karenina, by Leo Tolstoy
  • +
  • z-index page
  • Test PDF documents

    diff --git a/src/annotator/adder.js b/src/annotator/adder.js index c64b9ee975c..5c64b2af48d 100644 --- a/src/annotator/adder.js +++ b/src/annotator/adder.js @@ -89,10 +89,6 @@ export class Adder { // take position out of layout flow initially position: 'absolute', top: 0, - - // Assign a high Z-index so that the adder shows above any content on the - // page - zIndex: 10000, }); this._view = /** @type {Window} */ (container.ownerDocument.defaultView); @@ -193,6 +189,51 @@ export class Adder { return { top, left, arrowDirection }; } + /** + * Find a Z index value that will cause the adder to appear on top of any + * content in the document when the adder is shown at (left, top). + * + * @param {number} left - Horizontal offset from left edge of viewport. + * @param {number} top - Vertical offset from top edge of viewport. + * @return {number} - greatest zIndex (default value of 1) + */ + _findZindex(left, top) { + if (document.elementsFromPoint === undefined) { + // In case of not being able to use `document.elementsFromPoint`, + // default to the large arbitrary number (2^15) + return 32768; + } + + const adderWidth = this._width(); + const adderHeight = this._height(); + + // Find the Z index of all the elements in the screen for five positions + // around the adder (left-top, left-bottom, middle-center, right-top, + // right-bottom) and use the greatest. + + // Unique elements so `getComputedStyle` is called the minimum amount of times. + const elements = new Set([ + ...document.elementsFromPoint(left, top), + ...document.elementsFromPoint(left, top + adderHeight), + ...document.elementsFromPoint( + left + adderWidth / 2, + top + adderHeight / 2 + ), + ...document.elementsFromPoint(left + adderWidth, top), + ...document.elementsFromPoint(left + adderWidth, top + adderHeight), + ]); + + const zIndexes = [...elements] + .map(element => +getComputedStyle(element).zIndex) + .filter(Number.isInteger); + + // Make sure the array contains at least one element, + // otherwise `Math.max(...[])` results in +Infinity + zIndexes.push(0); + + return Math.max(...zIndexes) + 1; + } + /** * Show the adder at the given position and with the arrow pointing in * `arrowDirection`. @@ -210,9 +251,12 @@ export class Adder { const positionedAncestor = nearestPositionedAncestor(this._container); const parentRect = positionedAncestor.getBoundingClientRect(); + const zIndex = this._findZindex(left, top); + Object.assign(this._container.style, { - top: toPx(top - parentRect.top), left: toPx(left - parentRect.left), + top: toPx(top - parentRect.top), + zIndex, }); this._isVisible = true; diff --git a/src/annotator/test/adder-test.js b/src/annotator/test/adder-test.js index 71db1fb6790..75aac1ccfbe 100644 --- a/src/annotator/test/adder-test.js +++ b/src/annotator/test/adder-test.js @@ -1,4 +1,6 @@ import { act } from 'preact/test-utils'; +import { createElement } from 'preact'; +import { mount } from 'enzyme'; import { Adder, ARROW_POINTING_UP, ARROW_POINTING_DOWN } from '../adder'; @@ -217,6 +219,95 @@ describe('Adder', () => { }); }); + describe('adder Z index', () => { + let container; + + function getAdderZIndex(left, top) { + adderCtrl.showAt(left, top); + return parseInt(adderEl.style.zIndex); + } + + beforeEach(() => { + container = document.createElement('div'); + document.body.appendChild(container); + }); + + afterEach(() => { + container.remove(); + }); + + it('returns default hard coded value if `document.elementsFromPoint` is not available', () => { + const elementsFromPointBackup = document.elementsFromPoint; + document.elementsFromPoint = undefined; + assert.strictEqual(getAdderZIndex(0, 0), 32768); + document.elementsFromPoint = elementsFromPointBackup; + }); + + it('returns default value of 1', () => { + // Even if not elements are found, it returns 1 + assert.strictEqual(getAdderZIndex(-100000, -100000), 1); + assert.strictEqual(getAdderZIndex(100000, 100000), 1); + }); + + it('returns the greatest zIndex', () => { + const createComponent = (left, top, zIndex, attachTo) => + mount( +
    , + { attachTo } + ); + + const wrapper = createComponent(0, 0, 2, container); + assert.strictEqual(getAdderZIndex(0, 0), 3); + + const initLeft = 10; + const initTop = 10; + const adderWidth = adderCtrl._width(); + const adderHeight = adderCtrl._height(); + const wrapperDOMNode = wrapper.getDOMNode(); + + // Create first element (left-top) + createComponent(initLeft, initTop, 3, wrapperDOMNode); + assert.strictEqual(getAdderZIndex(initLeft, initTop), 4); + + // Create second element (left-bottom) + createComponent(initLeft, initTop + adderHeight, 5, wrapperDOMNode); + assert.strictEqual(getAdderZIndex(initLeft, initTop), 6); + + // Create third element (middle-center) + createComponent( + initLeft + adderWidth / 2, + initTop + adderHeight / 2, + 6, + wrapperDOMNode + ); + assert.strictEqual(getAdderZIndex(initLeft, initTop), 7); + + // Create fourth element (right-top) + createComponent(initLeft + adderWidth, initTop, 7, wrapperDOMNode); + assert.strictEqual(getAdderZIndex(initLeft, initTop), 8); + + // Create third element (right-bottom) + createComponent( + initLeft + adderWidth, + initTop + adderHeight, + 8, + wrapperDOMNode + ); + assert.strictEqual(getAdderZIndex(initLeft, initTop), 9); + + wrapper.unmount(); + }); + }); + describe('#showAt', () => { context('when the document and body elements have no offset', () => { it('shows adder at target position', () => {