0

I'm working on an application that requires the use of d3.zoom, and I'm bumping into an issue when listening for the mousedown event. I've simplified the problem into the example below.

Basically, I need parent to act on mousedown events. However, its grandchild has d3.zoom applied to it. And it appears that d3.zoom prevents mousedown events from bubbling up. So if I click anywhere inside of grandchild, parent won't receive mousedown.

One solution I tried that seems to work is to dispatch mousedown inside of the zoom start event handler to allow the event to continue bubbling up. But I'm wondering if there is a better solution. Is it considered bad practice to dispatch native JS events? Also, does anyone know if it's by design for d3.zoom to stop propagation of 'mousedown'?

.child {
  width: 100px;
  height: 100px;
  background-color: red;
}

.grandchild {
  width: 50px;
  height: 50px;
  background-color: blue;
}

.parent {
  width: 200px;
  height: 200px;
  background-color: yellow;
}
<div class='parent'>
  <div class='child'>
    <div class='grandchild'></div>
  </div>
</div>

<script type="module">
  import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

  const zoom = d3.zoom()
    .on('start', function (event) {
      // Dispatch 'mousedown' so that it can continue propagating. 
      if (event?.sourceEvent?.type === 'mousedown') {
        d3.select(this.parentNode).dispatch('mousedown', { bubbles: true })
      }

      console.log('start zoom')
    })
    .on('zoom', function () {
      console.log('zooming')
    })
    .on('end', function () {
      console.log('end zoom')
    })

  d3.select('.grandchild').call(zoom)

  d3.select('.parent').on('mousedown', function () {
    console.log('parent received mousedown')
  })
</script>

1 Answer 1

2

You can use event capturing, it's part of the event lifecycle and it starts before the bubbling up. It's like propagation but the other way around. Learn more: https://www.geeksforgeeks.org/phases-of-javascript-event/

In this example, I am capturing (using the true parameter) the event from the parent, before it reaches the grand-child's zoom.

document.querySelector(".parent").addEventListener('mousedown', function(ev) {
  console.log('captured mousedown on `.parent`, before propagation')
  console.log(ev.target)
  console.log(ev.currentTarget)
}, true);
.child {
  width: 100px;
  height: 100px;
  background-color: red;
}

.grandchild {
  width: 50px;
  height: 50px;
  background-color: blue;
}

.parent {
  width: 200px;
  height: 200px;
  background-color: yellow;
}
<div class='parent'>
  <div class='child'>
    <div class='grandchild'></div>
  </div>
</div>

<script type="module">
  import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

  const zoom = d3.zoom()
    .on('start', function (event) {
      // Dispatch 'mousedown' so that it can continue propagating. 
//      if (event?.sourceEvent?.type === 'mousedown') {
//        d3.select(this.parentNode).dispatch('mousedown', { bubbles: true })
//      }

      console.log('start zoom')
    })
    .on('zoom', function () {
      console.log('zooming')
    })
    .on('end', function () {
      console.log('end zoom')
    })

  d3.select('.grandchild').call(zoom)

  d3.select('.parent').on('mousedown', function () {
    console.log('parent received mousedown')
  })
</script>

Not the answer you're looking for? Browse other questions tagged or ask your own question.