diff --git a/src/core/dom.js b/src/core/dom.js index 2dbcfa451..ee5c3480b 100644 --- a/src/core/dom.js +++ b/src/core/dom.js @@ -143,6 +143,63 @@ const create_from_string = (string) => { return document.createRange().createContextualFragment(string.trim()); }; +/** + * Return a CSS property value for a given DOM node. + * For length-values, relative values are converted to pixels. + * Optionally parse as pixels, if applicable. + * + * Note: The element must be attached to the body to make CSS caluclations work. + * + * @param {Node} el - DOM node. + * @param {String} property - CSS property to query on DOM node. + * @param {Boolean} [as_pixels=false] - Convert value to pixels, if applicable. + * @param {Boolean} [as_float=false] - Convert value to float, if applicable. + * + * @returns {(String|Number)} - The CSS value to return. + */ +function get_css_value(el, property, as_pixels = false, as_float = false) { + let value = window.getComputedStyle(el).getPropertyValue(property); + if (as_pixels || as_float) { + value = parseFloat(value) || 0.0; + } + if (as_pixels && !as_float) { + value = parseInt(Math.round(value), 10); + } + return value; +} + +/** + * Find a scrollable element up in the DOM tree. + * + * Note: Setting the ``overflow`` shorthand property also sets the individual overflow-y and overflow-y properties. + * + * @param {Node} el - The DOM element to start the search on. + * @param {String} [direction=] - Not given: Search for any scrollable element up in the DOM tree. + * ``x``: Search for a horizontally scrollable element. + * ``y``: Search for a vertically scrollable element. + * + * @returns {Node} - Return the first scrollable element. + * If no other element could be found, document.body would be returned. + */ +const find_scroll_container = (el, direction) => { + while (el && el !== document.body) { + if (!direction || direction === "y") { + let overflow_y = get_css_value(el, "overflow-y"); + if (["auto", "scroll"].includes(overflow_y)) { + return el; + } + } + if (!direction || direction === "x") { + let overflow_x = get_css_value(el, "overflow-x"); + if (["auto", "scroll"].includes(overflow_x)) { + return el; + } + } + el = el.parentElement; + } + return el; +}; + const dom = { toNodeArray: toNodeArray, querySelectorAllAndMe: querySelectorAllAndMe, @@ -155,6 +212,8 @@ const dom = { acquire_attribute: acquire_attribute, is_visible: is_visible, create_from_string: create_from_string, + get_css_value: get_css_value, + find_scroll_container: find_scroll_container, add_event_listener: events.add_event_listener, // BBB export. TODO: Remove in an upcoming version. remove_event_listener: events.remove_event_listener, // BBB export. TODO: Remove in an upcoming version. }; diff --git a/src/core/dom.test.js b/src/core/dom.test.js index a657eed5e..f8f9ca8b4 100644 --- a/src/core/dom.test.js +++ b/src/core/dom.test.js @@ -484,4 +484,203 @@ describe("core.dom tests", () => { done(); }); }); + + describe("get_css_value", function () { + beforeEach(function () { + document.body.innerHTML = ` +
+
+
+
+ `; + }); + + afterEach(function () { + document.body.innerHTML = ""; + }); + + it("Return values for CSS properties of a HTML node", function () { + const el1 = document.querySelector("#el1"); + expect(dom.get_css_value(el1, "font-size")).toBe("12px"); + expect(dom.get_css_value(el1, "font-size", true)).toBe(12); + expect(dom.get_css_value(el1, "position")).toBe("relative"); + }); + + it("Return string, int or float, as requested.", function () { + const el1 = document.querySelector("#el1"); + expect(dom.get_css_value(el1, "font-size")).toBe("12px"); + expect(dom.get_css_value(el1, "font-size", true)).toBe(12); + expect(dom.get_css_value(el1, "font-size", true, true)).toBe(12.0); + expect(dom.get_css_value(el1, "font-size", null, true)).toBe(12.0); + }); + + it("Returns 0 for when requesting a numerical value which doesn't exist.", function () { + const el = document.createElement("div"); + expect(dom.get_css_value(el, "hallo", true)).toBe(0); + expect(dom.get_css_value(el, "hallo", true, true)).toBe(0.0); + expect(dom.get_css_value(el, "hallo", null, true)).toBe(0.0); + }); + + it.skip("Return inherited values for CSS properties", function () { + // Missing JSDOM support for style inheritance yet. See: + // https://github.com/jsdom/jsdom/issues/2160 + // https://github.com/jsdom/jsdom/pull/2668 + // https://github.com/jsdom/jsdom/blob/master/Changelog.md + + const el2 = document.querySelector("#el2"); + expect(dom.get_css_value(el2, "font-size")).toBe("12px"); + }); + + it.skip("Shorthand properties are split up", function () { + // Missing JSDOM support for property split yet. + + const el1 = document.querySelector("#el1"); + // ``em`` are parsed to pixel values. + // shorthand property sets like ``border`` are split up into their + // individual properties, like ``border-top-width``. + expect(dom.get_css_value(el1, "border-top-width")).toBe("12px"); + expect(dom.get_css_value(el1, "border-top-style")).toBe("solid"); + expect(dom.get_css_value(el1, "border-top-color")).toBe("rgb(0, 0, 0)"); + }); + + it.skip("Values with relative units are converted to pixels", function () { + // Missing JSDOM support for unit conversion yet. + + const el1 = document.querySelector("#el1"); + const el2 = document.querySelector("#el2"); + // Relative length-type values are converted to absolute pixels. + expect(dom.get_css_value(el1, "margin-top")).toBe("12px"); + expect(dom.get_css_value(el1, "margin-top", true)).toBe(12); + expect(dom.get_css_value(el2, "margin-top", true)).toBe(0); + expect(dom.get_css_value(el2, "margin-bottom")).toBe("24px"); + expect(dom.get_css_value(el2, "margin-bottom", true)).toBe(24); + }); + }); + + describe("find_scroll_container", function () { + it("finds itself", function (done) { + document.body.innerHTML = ` +
+
+
+
+ `; + const div2 = document.querySelector("#div2"); + expect(dom.find_scroll_container(div2)).toBe(div2); + done(); + }); + it("finds a scrollable parent", function (done) { + document.body.innerHTML = ` +
+
+
+
+ `; + const div1 = document.querySelector("#div1"); + const div2 = document.querySelector("#div2"); + expect(dom.find_scroll_container(div2)).toBe(div1); + done(); + }); + it("finds any scrolling direction", function (done) { + document.body.innerHTML = ` +
+
+
+
+ `; + const div1 = document.querySelector("#div1"); + const div2 = document.querySelector("#div2"); + expect(dom.find_scroll_container(div2)).toBe(div1); + done(); + }); + it.skip("finds any scrolling direction with generic overflow property", function (done) { + // Skipped due to jsDOM not setting overflow-x or overflow-y when only overflow is set. + document.body.innerHTML = ` +
+
+
+
+ `; + const div1 = document.querySelector("#div1"); + const div2 = document.querySelector("#div2"); + expect(dom.find_scroll_container(div2)).toBe(div1); + done(); + }); + it("finds only scrolling direction y", function (done) { + document.body.innerHTML = ` +
+
+
+
+
+
+ `; + const div1 = document.querySelector("#div1"); + const div3 = document.querySelector("#div3"); + expect(dom.find_scroll_container(div3, "y")).toBe(div1); + done(); + }); + it("finds only scrolling direction x", function (done) { + document.body.innerHTML = ` +
+
+
+
+
+
+ `; + const div1 = document.querySelector("#div1"); + const div3 = document.querySelector("#div3"); + expect(dom.find_scroll_container(div3, "x")).toBe(div1); + done(); + }); + it("returns document.body if nothing else is found", function (done) { + document.body.innerHTML = ` +
+
+
+
+ `; + const div2 = document.querySelector("#div2"); + expect(dom.find_scroll_container(div2, "y")).toBe(document.body); + done(); + }); + }); }); diff --git a/src/core/utils.js b/src/core/utils.js index 796a9f7fa..d774ee207 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -469,21 +469,6 @@ function findRelatives(el) { return $relatives; } -function getCSSValue(el, property, as_pixels = false, as_float = false) { - /* Return a CSS property value for a given DOM node. - * For length-values, relative values are converted to pixels. - * Optionally parse as pixels, if applicable. - */ - let value = window.getComputedStyle(el).getPropertyValue(property); - if (as_pixels || as_float) { - value = parseFloat(value) || 0.0; - } - if (as_pixels && !as_float) { - value = parseInt(Math.round(value), 10); - } - return value; -} - function get_bounds(el) { // Return bounds of an element with it's values rounded and converted to ints. const bounds = el.getBoundingClientRect(); @@ -663,7 +648,6 @@ var utils = { hasValue: hasValue, parseTime: parseTime, findRelatives: findRelatives, - getCSSValue: getCSSValue, get_bounds: get_bounds, checkInputSupport: checkInputSupport, checkCSSFeature: checkCSSFeature, @@ -676,6 +660,7 @@ var utils = { localized_isodate: localized_isodate, escape_html: escape_html, unescape_html: unescape_html, + getCSSValue: dom.get_css_value, // BBB: moved to dom. TODO: Remove in upcoming version. }; export default utils; diff --git a/src/core/utils.test.js b/src/core/utils.test.js index d309df3c7..534b57bd6 100644 --- a/src/core/utils.test.js +++ b/src/core/utils.test.js @@ -518,87 +518,6 @@ describe("parseTime", function () { }); }); -describe("getCSSValue", function () { - beforeEach(function () { - const el1 = document.createElement("div"); - const el2 = document.createElement("div"); - - el1.setAttribute("id", "el1"); - el2.setAttribute("id", "el2"); - el1.appendChild(el2); - - // Need to attach element to body to make CSS calculation work. - document.body.appendChild(el1); - - el1.style["font-size"] = "12px"; - el1.style["margin-top"] = "1em"; - el1.style.border = "1em solid black"; - el1.style.position = "relative"; - el2.style["margin-bottom"] = "2em"; - }); - - afterEach(function () { - document.querySelector("#el1").remove(); - }); - - it("Return values for CSS properties of a HTML node", function () { - const el1 = document.querySelector("#el1"); - expect(utils.getCSSValue(el1, "font-size")).toBe("12px"); - expect(utils.getCSSValue(el1, "font-size", true)).toBe(12); - expect(utils.getCSSValue(el1, "position")).toBe("relative"); - }); - - it("Return string, int or float, as requested.", function () { - const el1 = document.querySelector("#el1"); - expect(utils.getCSSValue(el1, "font-size")).toBe("12px"); - expect(utils.getCSSValue(el1, "font-size", true)).toBe(12); - expect(utils.getCSSValue(el1, "font-size", true, true)).toBe(12.0); - expect(utils.getCSSValue(el1, "font-size", null, true)).toBe(12.0); - }); - - it("Returns 0 for when requesting a numerical value which doesn't exist.", function () { - const el = document.createElement("div"); - expect(utils.getCSSValue(el, "hallo", true)).toBe(0); - expect(utils.getCSSValue(el, "hallo", true, true)).toBe(0.0); - expect(utils.getCSSValue(el, "hallo", null, true)).toBe(0.0); - }); - - it.skip("Return inherited values for CSS properties", function () { - // Missing JSDOM support for style inheritance yet. See: - // https://github.com/jsdom/jsdom/issues/2160 - // https://github.com/jsdom/jsdom/pull/2668 - // https://github.com/jsdom/jsdom/blob/master/Changelog.md - - const el2 = document.querySelector("#el2"); - expect(utils.getCSSValue(el2, "font-size")).toBe("12px"); - }); - - it.skip("Shorthand properties are split up", function () { - // Missing JSDOM support for property split yet. - - const el1 = document.querySelector("#el1"); - // ``em`` are parsed to pixel values. - // shorthand property sets like ``border`` are split up into their - // individual properties, like ``border-top-width``. - expect(utils.getCSSValue(el1, "border-top-width")).toBe("12px"); - expect(utils.getCSSValue(el1, "border-top-style")).toBe("solid"); - expect(utils.getCSSValue(el1, "border-top-color")).toBe("rgb(0, 0, 0)"); - }); - - it.skip("Values with relative units are converted to pixels", function () { - // Missing JSDOM support for unit conversion yet. - - const el1 = document.querySelector("#el1"); - const el2 = document.querySelector("#el2"); - // Relative length-type values are converted to absolute pixels. - expect(utils.getCSSValue(el1, "margin-top")).toBe("12px"); - expect(utils.getCSSValue(el1, "margin-top", true)).toBe(12); - expect(utils.getCSSValue(el2, "margin-top", true)).toBe(0); - expect(utils.getCSSValue(el2, "margin-bottom")).toBe("24px"); - expect(utils.getCSSValue(el2, "margin-bottom", true)).toBe(24); - }); -}); - describe("get_bounds", function () { it("returns the bounds values as integer numbers instead of double/float values.", () => { const el = document.createElement("div"); diff --git a/src/pat/bumper/bumper.js b/src/pat/bumper/bumper.js index 6c68abb20..05ef6e8a7 100644 --- a/src/pat/bumper/bumper.js +++ b/src/pat/bumper/bumper.js @@ -3,6 +3,7 @@ */ import Base from "../../core/base"; +import dom from "../../core/dom"; import logging from "../../core/logging"; import Parser from "../../core/parser"; import utils from "../../core/utils"; @@ -42,14 +43,14 @@ export default Base.extend({ }, _init() { - const scroll_container_y = this.findScrollContainer("y"); - const scroll_container_x = this.findScrollContainer("x"); + const scroll_container_y = dom.find_scroll_container(this.el.parentElement, "y"); + const scroll_container_x = dom.find_scroll_container(this.el.parentElement, "x"); const pos = { - top: utils.getCSSValue(this.el, "top", true), - right: utils.getCSSValue(this.el, "right", true), - bottom: utils.getCSSValue(this.el, "bottom", true), - left: utils.getCSSValue(this.el, "left", true), + top: dom.get_css_value(this.el, "top", true), + right: dom.get_css_value(this.el, "right", true), + bottom: dom.get_css_value(this.el, "bottom", true), + left: dom.get_css_value(this.el, "left", true), }; const intersection_observer_config_y = { threshold: [1, 0.99, 0.97, 0.96, 0.95, 0.94, 0.93, 0.92, 0.91, 0.9], @@ -135,25 +136,4 @@ export default Base.extend({ } } }, - - findScrollContainer(direction = null) { - let parent = this.el.parentElement; - let overflow; - while (parent && parent !== document.body) { - if (!direction || direction === "y") { - overflow = utils.getCSSValue(parent, "overflow-y"); - if (overflow === "auto" || overflow === "scroll") { - return parent; - } - } - if (!direction || direction === "x") { - overflow = utils.getCSSValue(parent, "overflow-x"); - if (overflow === "auto" || overflow === "scroll") { - return parent; - } - } - parent = parent.parentElement; - } - return null; - }, }); diff --git a/src/pat/gallery/gallery.js b/src/pat/gallery/gallery.js index 1258d8948..277243bf6 100644 --- a/src/pat/gallery/gallery.js +++ b/src/pat/gallery/gallery.js @@ -156,7 +156,7 @@ export default Base.extend({ gallery.listen("initialZoomInEnd", () => { // don't show body scrollbars when overlay is open - this.orig_body_overflow = utils.getCSSValue(document.body, "overflow"); + this.orig_body_overflow = dom.get_css_value(document.body, "overflow"); document.body.style.overflow = "hidden"; }); diff --git a/src/pat/inject/index.html b/src/pat/inject/index.html index 7ca6e5b82..ac603f20b 100644 --- a/src/pat/inject/index.html +++ b/src/pat/inject/index.html @@ -1,14 +1,14 @@ - + - - - - - - + + + + + + Injection pattern