javascriptblocklygoogle-blockly

Blockly element prevents scrolling when mouse is hovering over it


enter image description here

As seen in the image. If you hover over the gray area it prevents scrolling. We're using this in an app where our blockly instance is much larger. This is confusing our clients. Is there a way we could make this blockly component behave more like normal html elements?

Try it for yourself here: https://developers.google.com/blockly

I've tried selecting all the element causing the problem.

const elements = document.getElementsByClassName("blocklyFlyout")

Then removing all event listeners from those elements

elements.forEach(old => {
    const cloned = old.cloneNode(true)
    old.parentNode.replaceChild(cloned, old)
})

This allows scrolling to work again but disables scrolling inside of the blockly element if it overflows.

EDIT: Partial solution. This makes scrolling work but breaks the scrolling for blockly component

const targetElement = document.documentElement;
const elements = document.getElementsByClassName("blocklyFlyout");
for (const element of elements) {
  element.addEventListener(
    "wheel",
    function (event) {
      targetElement.scrollTop += event.deltaY;
    },
    { passive: true }
  );
}

Solution

  • This is very hacky but it works. I wish there was configurations I could put into blockly to get the functionality I want but whatever.

    /*
    
    Problems:
    
    1.) This script is highly dependent on blockly so it might break if blockly changes.
    
    
    */
    var isRemappingScrolling = false;
    var removeListeners;
    
    onAttributeChange(
      document.querySelectorAll(".blocklyScrollbarHandle"),
      function (mutation) {
        var scrollbarHandle = mutation.target;
        var scrollbar = scrollbarHandle.parentElement.parentElement;
    
        if (isAtTopOrBottom(scrollbar, scrollbarHandle) && !isRemappingScrolling) {
          isRemappingScrolling = true;
          removeListeners = remapScrolling(
            document.querySelectorAll(".blocklyFlyout"),
            document.documentElement
          );
          console.log("started remapping scrolling");
        }
    
        if (!isAtTopOrBottom(scrollbar, scrollbarHandle) && isRemappingScrolling) {
          isRemappingScrolling = false;
          if (typeof removeListeners === "function") {
            removeListeners();
          }
          console.log("stopped remapping scrolling");
        }
      }
    );
    
    /*
    
    
    
    Helpers
    
    
    
    */
    
    /**
     * @param {SVGElement} parentSvg
     * @param {SVGElement} childSvg
     * @returns {boolean}
     */
    function isAtTopOrBottom(parentSvg, childSvg) {
      try {
        var childHeight = parseFloat(childSvg.getAttribute("height"));
        var childY = parseFloat(childSvg.getAttribute("y"));
        var parentHeight = parseFloat(parentSvg.getAttribute("height"));
        var isAtBottom = childY + childHeight >= parentHeight;
        var isAtTop = childY <= 0;
        return isAtBottom || isAtTop;
      } catch (e) {
        return false;
      }
    }
    
    /**
     * @param {HTMLElement} destinationNode
     * @param {Iterable<HTMLElement>} sourceNodes
     * @returns {Function}
     * @description
     * This function will remap the scrolling of the destinationNode to the sourceNodes.
     * It will return a function that will remove the listeners.
     */
    function remapScrolling(sourceNodes, destinationNode) {
      function mouseWheelHandler(event) {
        destinationNode.scrollTop += event.deltaY;
      }
    
      for (var node of sourceNodes) {
        node.addEventListener("wheel", mouseWheelHandler, { passive: true });
      }
    
      return function removeListeners() {
        for (var node of sourceNodes) {
          node.removeEventListener("wheel", mouseWheelHandler);
        }
      };
    }
    
    /**
     * @callback MutationSubscriber
     * @param  {MutationRecord} mutation
     * @returns {void}
     **/
    
    /**
     * @param {Iterable<HTMLElement>} nodeList
     * @param {MutationSubscriber} subscriber
     * @returns {Function}
     * @description
     * This function will subscribe to the attribute changes of the nodeList.
     * It will return a function that will disconnect the observers.
     */
    function onAttributeChange(nodeList, subscriber) {
      var observers = [];
    
      for (var node of nodeList) {
        var observer = new MutationObserver(function callback(mutationsList) {
          for (var mutation of mutationsList) {
            subscriber(mutation);
          }
        });
        observer.observe(node, {
          attributes: true,
        });
        observers.push(observer);
      }
    
      return function disconnect() {
        for (var observer of observers) {
          observer.disconnect();
        }
      };
    }