vue-select icon indicating copy to clipboard operation
vue-select copied to clipboard

prevent click propogation of options

Open srchulo opened this issue 5 years ago • 12 comments

To Reproduce

<!-- method should never be called when clicking an option -->
<div @click="method">

    <!-- this should prevent click propogation of options -->
    <div @click.stop>
        <v-select .../>
    </div>
</div>

Steps to reproduce the behavior:

  1. Have a click listener on an item wrapping v-select
  2. Wrap v-select with a div and use @click.stop
  3. Click the dropdown
  4. Click an option

Expected behavior Clicking an option should not trigger the parent click event, since it is a child of the div that has @click.stop. However, a click event is triggered. One is not for the dropdown itself, maybe because it's not dynamic? In inspector, the dropdown options are contained within the div, but seem to float outside of it.

@mousedown seems to work for my use case, maybe because @mousedown.prevent is used here?

https://github.com/sagalbot/vue-select/blob/6e9861e787d016239df2f73ddf18bea42704db07/src/components/Select.vue#L67

It would be great if there was a way to also prevent clicks from propogating.

srchulo avatar Aug 25 '20 00:08 srchulo

Maybe the options should prevent/stop all three of these?

  • @mousedown
  • @mouseup
  • @click

srchulo avatar Aug 25 '20 01:08 srchulo

Also, it looks like clicking on the dropdown menu itself doesn't stop click propogation:

https://github.com/sagalbot/vue-select/blob/6e9861e787d016239df2f73ddf18bea42704db07/src/components/Select.vue#L56

srchulo avatar Aug 25 '20 02:08 srchulo

This has posed an interesting problem.

Say you have a modal with a backdrop, and user can click backdrop to close modal. In that modal, you also have a dropdown menu, whose length exceeds that of the modal.

In previous versions of vue-select, this did not seem to be an issue. But now, since the click event of an option is bubbled upwards, the modal's backdrop gets the click, and the modal closes.

alex-dow avatar Aug 31 '20 16:08 alex-dow

@alex-dow : yes, I was just going to post an issue about modal closing unexpectedly...

guillaumejay avatar Sep 01 '20 12:09 guillaumejay

I have a similar issue, click event and the click-outside solution I am using clash. My modal opens for a brief second, then it disappears when the click event bubbles and kicks in. Has anybody found a solution?

NataleCatella avatar Sep 03 '20 09:09 NataleCatella

Hmm.. I know this isn't a perfect solution but I also encountered this issue (and thought I was me who was doing something wrong) when I wanted to place a dropdown inside a bootstrap modal. I added data-backdrop="static" data-keyboard="false" to the button responsible for triggering the modal, which prevents it from disappearing and stays there unless it is specifically closed with a dedicated button. I understand it not the best solution and just a mere workaround but that's a sacrifice I am willing to make for now 🤷 ...

michmaml avatar Oct 31 '20 21:10 michmaml

I also ran into this issue while using vue-dismiss, and I tried a lot of things to stop the option click event propagating but had no luck.

My solution was to create a custom version of vue-dismiss that also has an update() hook and lets you pass in an active value (which you can bind to a reactive value that updates on vue-select's @open / @close events). I'm not actually sure why vue-dismiss doesn't have an update() hook, makes the watch value you can pass in kind of useless as it is only used during initial binding of the directive.

Custom vue-dismiss

import Vue from 'vue'

const elementActive = '_vueDismissActive'
const elementClickKey = '_vueDismissClick'
const elementKeyupKey = '_vueDismissKeyup'

Vue.directive('onDismiss', {
  bind(el, binding) {
    const callback = typeof binding.value === 'function' ? binding.value : binding.value.callback
    el[elementActive] = Object.prototype.hasOwnProperty.call(binding.value, 'active')
      ? Boolean(binding.value.active)
      : true

    if (typeof document !== 'undefined' && !el[elementClickKey]) {
      const handler = function (event) {
        if (el[elementActive]) {
          if (event.keyCode) {
            event.keyCode === 27 && callback()
          } else if (!(event.target === el) && !el.contains(event.target)) {
            callback()
          }
        }
      }

      setTimeout(function() {
        document.addEventListener('mousedown', handler)
        document.addEventListener('keyup', handler)
      }, 10)

      el[elementClickKey] = el[elementKeyupKey] = handler
    }
  },
  unbind(el) {
    if (el[elementClickKey] && typeof document !== 'undefined') {
      document.removeEventListener('mousedown', el[elementClickKey])
      document.removeEventListener('keyup', el[elementKeyupKey])
      delete el[elementActive]
      delete el[elementClickKey]
      delete el[elementKeyupKey]
    }
  },
  update(el, binding) {
    setTimeout(function() {
      if (typeof binding.value !== 'function') {
        el[elementActive] = Object.prototype.hasOwnProperty.call(binding.value, 'active')
          ? Boolean(binding.value.active)
          : true
      }
    }, 250)
  }
})

Not an ideal solution, but it works well. A couple of things to note:

  • Events don't unbind when active is true, it just bypasses the event handler (unlike with the original vue-select's watch non-reactive value).
  • A delay is added to the code in the update hook, to prevent dismissing on clicks of different lengths. I experimented a bit and 250ms seems to work well, but you can change it if you are experiencing issues.

Basic Usage Example

<template>
  <div v-if="modalIsOpen">
    <div v-on-dismiss="{ callback: onDismiss, active: selectIsOpen }">
      <v-select @open="selectIsOpen = true" @close="selectIsOpen = false" />
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      modalIsOpen: true,
      selectIsOpen: false
    }
  },
  methods: {
    onDismiss() {
      this.modalIsOpen = false
    }
  }
}
</script>

slavanossar avatar Dec 17 '20 06:12 slavanossar

any solution to this?

rublev avatar May 03 '21 16:05 rublev

how to solve this, i'm using modal and will trigger close if the dropdown is on the top of backdrop area

bagaskarala avatar May 20 '21 06:05 bagaskarala

This worked for me:

<template>
  <VueSelect
    ,,,
    ref="vueSelect"
    @option:selecting="handleSelect"
  >
    ...
  </VueSelect>
...
...
methods: {
  handleSelect(selectedOption) {
    const { closeOnSelect, searchEl } = this.$refs.vueSelect;

    if (!closeOnSelect && searchEl) {
      searchEl.blur();
    }

    ...
  },
...

f0ll0wthewhiterabbit avatar Jun 25 '21 16:06 f0ll0wthewhiterabbit

I dont know if solve the issue, in a similar situation adding @click.stop="" preserve the select function without propagating the click event to outer components

LucaColombi avatar Nov 08 '22 09:11 LucaColombi