Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(click-outside): correctly remove shadow DOM event listeners #20273

Merged
merged 2 commits into from
Aug 3, 2024

Conversation

cgodo
Copy link
Contributor

@cgodo cgodo commented Aug 3, 2024

Description

ClickOutside directive uses global onmousedown and click events in the parent shadowRoot of the element when the element is inside the Shadow DOM.

When unmounted, the directive tries to remove those event handlers, but since the element is already unmounted, it can't find the parent shadowRoot, so the events are never removed.

This causes VOverlay content clicks to behave incorrectly, closing menus and submenus when they should stay open.

Using beforeUnmount instead of unmounted to remove event handlers in ClickOutside directive solves the issue.

fixes #19616

Markup:

<template>
  <v-menu v-bind="$attrs" location="right">
    <template #activator="{ props }">
      <v-btn v-bind="props" class="ma-5">Normal Menu</v-btn>
    </template>
    <v-list>
      <v-menu location="right">
        <template #activator="{ props }">
          <v-list-item v-bind="props" title="Submenu" />
        </template>
        <v-list>
          <v-list-item title="Submenu item" />
          <v-list-item title="Submenu item" />
        </v-list>
      </v-menu>
    </v-list>
  </v-menu>

  <div ref="shadow" />
</template>

<script>
  import { createApp, h } from 'vue'
  import vuetify from './vuetify'
  import { VBtn, VList, VListItem, VMenu } from 'vuetify/src/components'

  export default {
    name: 'Playground',
    setup () {
      return {

      }
    },
    mounted () {
      const shadowRoot = this.$refs.shadow.attachShadow({ mode: 'open' })
      const shadowApp = document.createElement('div')
      shadowApp.id = 'shadow-app'
      shadowRoot.appendChild(shadowApp)
      document.querySelectorAll('style').forEach(styleElement => {
        shadowRoot.appendChild(styleElement.cloneNode(true))
      })

      createApp({
        render: () => h(VMenu, { location: 'right' }, {
          activator: ({ props }) => h(VBtn, { ...props, class: 'ma-5' }, 'Shadow Menu'),
          default: () => [
            h(VList, {}, () => [
              h(VMenu, { location: 'right' }, {
                activator: ({ props }) => h(VListItem, { ...props, title: 'Submenu' }),
                default: () => [
                  h(VList, {}, () => [
                    h(VListItem, { title: 'Submenu item' }),
                    h(VListItem, { title: 'Submenu item' }),
                  ]),
                ],
              }),
            ]),
          ],
        }),
      })
        .use(vuetify)
        .mount(shadowApp)
    },
  }
</script>

Use beforeUnmount instead of unmounted to remove event handlers in
click-outside directive, since it's not possible to get element's
shadowRoot when the element is already unmounted.

fixes vuetifyjs#19616
@KaelWD KaelWD added this to the v3.6.x milestone Aug 3, 2024
@KaelWD KaelWD changed the title fix(click-outside): correctly remove event handlers in Shadow DOM fix(click-outside): correctly remove shadow DOM event listeners Aug 3, 2024
@KaelWD KaelWD merged commit 9b02b0d into vuetifyjs:master Aug 3, 2024
10 checks passed
@cgodo cgodo deleted the fix/19616-click-outside-shadow branch August 3, 2024 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants