Unverified Commit f8c91fdd authored by Lauri Timmanee's avatar Lauri Timmanee
Browse files

Issue #3256768 by mherchel, nod_, andregp, andy-blum: Drupal.displace() should...

Issue #3256768 by mherchel, nod_, andregp, andy-blum: Drupal.displace() should set CSS Variables indicating displacement values
parent e0730251
Loading
Loading
Loading
Loading
+88 −45
Original line number Diff line number Diff line
@@ -23,25 +23,66 @@
 *
 * @event drupalViewportOffsetChange
 */

(function ($, Drupal, debounce) {
  /**
   * @name Drupal.displace.offsets
   *
   * @type {Drupal~displaceOffset}
   */
  let offsets = {
    top: 0,
  const cache = {
    right: 0,
    bottom: 0,
    left: 0,
    bottom: 0,
    top: 0,
  };
  /**
   * The prefix used for the css custom variable name.
   *
   * @type {string}
   */
  const cssVarPrefix = '--drupal-displace-offset';
  const documentStyle = document.documentElement.style;
  const offsetKeys = Object.keys(cache);
  /**
   * The object with accessors that update the CSS variable on value update.
   *
   * @type {Drupal~displaceOffset}
   */
  const offsetProps = {};
  offsetKeys.forEach((edge) => {
    offsetProps[edge] = {
      // Show this property when using Object.keys().
      enumerable: true,
      get() {
        return cache[edge];
      },
      set(value) {
        // Only update the CSS custom variable when the value changed.
        if (value !== cache[edge]) {
          documentStyle.setProperty(`${cssVarPrefix}-${edge}`, `${value}px`);
        }
        cache[edge] = value;
      },
    };
  });

  /**
   * Current value of the size of margins on the page.
   *
   * This property is read-only and the object is sealed to prevent key name
   * modifications since key names are used to dynamically construct CSS custom
   * variable names.
   *
   * @name Drupal.displace.offsets
   *
   * @type {Drupal~displaceOffset}
   */
  const offsets = Object.seal(Object.defineProperties({}, offsetProps));

  /**
   * Calculates displacement for element based on its dimensions and placement.
   *
   * @param {HTMLElement} el
   *   The jQuery element whose dimensions and placement will be measured.
   *   The element whose dimensions and placement will be measured.
   *
   * @param {string} edge
   *   The name of the edge of the viewport that the element is associated
@@ -139,33 +180,23 @@
    return edgeOffset;
  }

  /**
   * Determines the viewport offsets.
   *
   * @return {Drupal~displaceOffset}
   *   An object whose keys are the for sides an element -- top, right, bottom
   *   and left. The value of each key is the viewport displacement distance for
   *   that edge.
   */
  function calculateOffsets() {
    return {
      top: calculateOffset('top'),
      right: calculateOffset('right'),
      bottom: calculateOffset('bottom'),
      left: calculateOffset('left'),
    };
  }

  /**
   * Informs listeners of the current offset dimensions.
   *
   * Corresponding CSS custom variables are also updated.
   * Corresponding CSS custom variables names are:
   *  - `--drupal-displace-offset-top`
   *  - `--drupal-displace-offset-right`
   *  - `--drupal-displace-offset-bottom`
   *  - `--drupal-displace-offset-left`
   *
   * @function Drupal.displace
   *
   * @prop {Drupal~displaceOffset} offsets
   *
   * @param {bool} [broadcast]
   *   When true or undefined, causes the recalculated offsets values to be
   *   broadcast to listeners.
   * @param {bool} [broadcast=true]
   *   When true, causes the recalculated offsets values to be
   *   broadcast to listeners. If none is given, defaults to true.
   *
   * @return {Drupal~displaceOffset}
   *   An object whose keys are the for sides an element -- top, right, bottom
@@ -174,10 +205,20 @@
   *
   * @fires event:drupalViewportOffsetChange
   */
  function displace(broadcast) {
    offsets = calculateOffsets();
    Drupal.displace.offsets = offsets;
    if (typeof broadcast === 'undefined' || broadcast) {
  function displace(broadcast = true) {
    const newOffsets = {};
    // Getting the offset and setting the offset needs to be separated because
    // of performance concerns. Only do DOM/style reading happening here.
    offsetKeys.forEach((edge) => {
      newOffsets[edge] = calculateOffset(edge);
    });
    // Once we have all the values, write to the DOM/style.
    offsetKeys.forEach((edge) => {
      // Updating the value in place also update Drupal.displace.offsets.
      offsets[edge] = newOffsets[edge];
    });

    if (broadcast) {
      $(document).trigger('drupalViewportOffsetChange', offsets);
    }
    return offsets;
@@ -195,7 +236,6 @@
        return;
      }
      this.displaceProcessed = true;

      $(window).on('resize.drupalDisplace', debounce(displace, 200));
    },
  };
@@ -206,19 +246,22 @@
   * @ignore
   */
  Drupal.displace = displace;
  $.extend(Drupal.displace, {

  /**
   * Expose offsets to other scripts to avoid having to recalculate offsets.
   *
   * @ignore
   */
    offsets,
  Object.defineProperty(Drupal.displace, 'offsets', {
    value: offsets,
    // Make sure other scripts don't replace this object.
    writable: false,
  });

  /**
   * Expose method to compute a single edge offsets.
   *
   * @ignore
   */
    calculateOffset,
  });
  Drupal.displace.calculateOffset = calculateOffset;
})(jQuery, Drupal, Drupal.debounce);
+41 −20
Original line number Diff line number Diff line
@@ -6,12 +6,35 @@
**/

(function ($, Drupal, debounce) {
  let offsets = {
    top: 0,
  const cache = {
    right: 0,
    left: 0,
    bottom: 0,
    left: 0
    top: 0
  };
  const cssVarPrefix = '--drupal-displace-offset';
  const documentStyle = document.documentElement.style;
  const offsetKeys = Object.keys(cache);
  const offsetProps = {};
  offsetKeys.forEach(edge => {
    offsetProps[edge] = {
      enumerable: true,

      get() {
        return cache[edge];
      },

      set(value) {
        if (value !== cache[edge]) {
          documentStyle.setProperty(`${cssVarPrefix}-${edge}`, `${value}px`);
        }

        cache[edge] = value;
      }

    };
  });
  const offsets = Object.seal(Object.defineProperties({}, offsetProps));

  function getRawOffset(el, edge) {
    const $el = $(el);
@@ -69,20 +92,17 @@
    return edgeOffset;
  }

  function calculateOffsets() {
    return {
      top: calculateOffset('top'),
      right: calculateOffset('right'),
      bottom: calculateOffset('bottom'),
      left: calculateOffset('left')
    };
  }

  function displace(broadcast) {
    offsets = calculateOffsets();
    Drupal.displace.offsets = offsets;
  function displace() {
    let broadcast = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
    const newOffsets = {};
    offsetKeys.forEach(edge => {
      newOffsets[edge] = calculateOffset(edge);
    });
    offsetKeys.forEach(edge => {
      offsets[edge] = newOffsets[edge];
    });

    if (typeof broadcast === 'undefined' || broadcast) {
    if (broadcast) {
      $(document).trigger('drupalViewportOffsetChange', offsets);
    }

@@ -101,8 +121,9 @@

  };
  Drupal.displace = displace;
  $.extend(Drupal.displace, {
    offsets,
    calculateOffset
  Object.defineProperty(Drupal.displace, 'offsets', {
    value: offsets,
    writable: false
  });
  Drupal.displace.calculateOffset = calculateOffset;
})(jQuery, Drupal, Drupal.debounce);
 No newline at end of file
+5 −0
Original line number Diff line number Diff line
name: 'JS Displace Tests'
type: module
description: 'Support module for testing Drupal.displace() JavaScript API.'
package: Testing
version: VERSION
+13 −0
Original line number Diff line number Diff line
<?php

/**
 * @file
 * Functions to support testing Drupal.displace() JavaScript API.
 */

/**
 * Implements hook_preprocess_html().
 */
function js_displace_preprocess_html(&$variables) {
  $variables['#attached']['library'][] = 'core/drupal.displace';
}
+126 −0
Original line number Diff line number Diff line
const testValues = {
  top: 200,
  right: 110,
  bottom: 145,
  left: 310,
};

const testElements = `
  <div
    data-offset-top
    style="
      background-color: red;
      height: 110px;
      left: 0;
      position: fixed;
      right: 0;
      top: 90px;
      width: 100%;"
  ></div>

  <div
    data-offset-right
    style="
      background-color: blue;
      bottom: 0;
      height: 100%;
      position: fixed;
      right: 10px;
      top: 0;
      width: 100px;"
  ></div>

  <div
    data-offset-bottom
    style="
      background-color: yellow;
      bottom: 45px;
      height: 100px;
      left: 0;
      position: fixed;
      right: 0;
      width: 100%;"
  ></div>

  <div
    data-offset-left
    style="
      background-color: orange;
      bottom: 0;
      height: 100%;
      left: 10px;
      position: fixed;
      top: 0;
      width: 300px;"
  ></div>
`;

module.exports = {
  '@tags': ['core'],
  before(browser) {
    browser.drupalInstall().drupalInstallModule('js_displace');
  },
  after(browser) {
    browser.drupalUninstall();
  },
  'Test Drupal.displace() JavaScript API': (browser) => {
    browser
      .drupalRelativeURL('/')
      .waitForElementVisible('body')
      .execute(
        // eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
        function (testValues, testElements) {
          const testElementsContainer = document.createElement('div');

          testElementsContainer.innerHTML = testElements;
          document.body.append(testElementsContainer);

          const displaceOutput = Drupal.displace();
          return (
            displaceOutput.top === testValues.top &&
            displaceOutput.right === testValues.right &&
            displaceOutput.bottom === testValues.bottom &&
            displaceOutput.left === testValues.left
          );
        },
        [testValues, testElements],
        (result) => {
          browser.assert.ok(
            result.value,
            'Drupal.displace() JS returns proper offsets for all edges.',
          );
        },
      )
      .execute(
        // eslint-disable-next-line func-names, prefer-arrow-callback, no-shadow
        function (testValues) {
          const rootStyles = getComputedStyle(document.documentElement);
          const topOffsetStyle = rootStyles.getPropertyValue(
            '--drupal-displace-offset-top',
          );
          const rightOffsetStyle = rootStyles.getPropertyValue(
            '--drupal-displace-offset-right',
          );
          const bottomOffsetStyle = rootStyles.getPropertyValue(
            '--drupal-displace-offset-bottom',
          );
          const leftOffsetStyle = rootStyles.getPropertyValue(
            '--drupal-displace-offset-left',
          );
          return (
            topOffsetStyle === `${testValues.top}px` &&
            rightOffsetStyle === `${testValues.right}px` &&
            bottomOffsetStyle === `${testValues.bottom}px` &&
            leftOffsetStyle === `${testValues.left}px`
          );
        },
        [testValues],
        (result) => {
          browser.assert.ok(
            result.value,
            'Drupal.displace() properly sets CSS variables.',
          );
        },
      );
  },
};