Vuetify: [Bug Report] Weird behaviour inside shadow DOM

Created on 21 Jun 2019  路  12Comments  路  Source: vuetifyjs/vuetify

Versions and Environment

Vuetify: 1.5.14
Vue: 2.6.10
Browsers: Google Chrome
OS: Linux x86_64

Steps to reproduce

  1. Render vuetify inside shadow dom
  2. Functionality becomes very limited, such as:

    • Cannot focus for text fields

    • Menuable items cannot be attached to VApp

    • Menuable items cannot be attached by selectors

Expected Behavior

Clicking on the text field should show that the text field is focused
The select list should work even without attach property

Actual Behavior

The text field does not react on clicks or activating via tab
The select list does not open without attach property

Reproduction Link

https://codepen.io/anon/pen/rEjXRj

Other comments

Possible culprit:

  • document.activeElement inside of onFocus in VTextField does not work in shadow dom
  • document.querySelector does not work in shadow dom

Possible solutions:

  • calculate actual root with getRootNode (might require polyfill on Edge and IE11)
  • track VApp and use querySelector directly on the VApp element

I could not find any workarounds that vuetify can offer for this issue at this moment.

Framework enhancement

Most helpful comment

There are other consideration than only the use of document. For example, the use of rem css units will prevent overriding the font-size of elements inside the shadow dom.

I don't expect the Vuetify team to implement these changes in Vuetify 2.x (although it would be nice). But you guys should consider this when writing Vuetify 3. IMO, a proper framework should not assume that it is the only think running on the page and allow to be scoped to a particular DOM element.

All 12 comments

There are no plans currently to support web components

See also #4075, #5054, #6203

@KaelWD The issue is not about web components. It is about handling the case when the vuetify app is rendered inside shadow DOM

Yeah sorry I didn't really look to closely because it seemed very similar to other issues at first. The closest one is probably #6203 as we are using document directly and not accounting for shadow DOM.
A solution to this would be for use to replace document with getRootNode and a polyfill for IE. The second suggestion wouldn't for for the reason stated in #6203.

Followed the web of issues and feel this is the right place to jump in. Super interested in this and willing to have our team dedicate some time to it, but this would be our first venture in to this codebase (though we've contributed to plenty of other OSS in various minor capacities) and might need a little support getting started.

Took a super precursory grep document and realized it doesn't seem like a clean cut case across the entire library, even though there's less than a hundred references (tests included). @KaelWD could you or others help craft a strategy on how best to tackle this? Assuming a well placed getRootNode on the lib or config would get us far, but would love even just a 30k foot view of how best to target it.

Took a pretty deep dive today and it looks manageable. Excluding the specs/tests, I had less than 90 refs to document and many of which are not what we need to target. I still don't fully grok the "why" on some of these calls, and can't help thinking that while we're in here trying to do this it might make sense to look at a more wholistic strategy for this kind of thing. Thoughts?

Any progress on this? This would be a pretty useful feature.

@TheInvoker Been wrapped up with a big release, but I'm actually hoping to take a stab at it on Sunday. Happy to collaborate on it if you'd like

In the meantime, what you can do is:

const vuetify = new Vuetify(options)

// @hack: Make sure we don't re-use the page's current style element
vuetify.framework.theme.checkOrCreateStyleElement = function () {
  if (!this.styleEl) this.genStyleElement()
  return Boolean(this.styleEl)
}

const app = new Vue({ ..., vuetify })

const shadowHost = document.createElement('div')
// Add shadow host wherever you want in the DOM
document.body.appendChild(shadowHost)

const shadowRoot = shadowHost.attachShadow({ mode: 'closed' })

// Move vuetify theme style element from document.head into shadowDom
const { styleEl } = vue.$vuetify.theme
styleEl.remove()
shadowRoot.appendChild(styleEl)

// Monkey patch querySelector to properly find root element
const { querySelector } = document
document.querySelector = function (selector) {
  if (selector === '[data-app]') return shadowRoot
  return querySelector.call(this, selector)
}

app.$mount(shadowRoot.appendChild(document.createElement('div')))

This will break if there is more than one instance of Vuetify running on the page.

There are other consideration than only the use of document. For example, the use of rem css units will prevent overriding the font-size of elements inside the shadow dom.

I don't expect the Vuetify team to implement these changes in Vuetify 2.x (although it would be nice). But you guys should consider this when writing Vuetify 3. IMO, a proper framework should not assume that it is the only think running on the page and allow to be scoped to a particular DOM element.

@matthieusieben I don't get why your workaround breaks with more than one Shadow DOM? Could you explain?

Do you mean more than one Shadow DOM that uses Vuetify? Even then the scoping in the #data-app seems sufficient (considering of course that the referenced id is specific to each instance). I'm very new to shadow DOM though so there is probably something I don't get.

@jggc updated my comment: It will break Vuetify if more than one instance of Vuetify runs on the page (which, in my case, happened to run from "inside" a shadow DOM)

PR #12134 fixes the "Cannot focus for text fields" issue.

Was this page helpful?
0 / 5 - 0 ratings