vuetify: [Bug Report] Weird behaviour inside shadow DOM

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.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 21 (10 by maintainers)

Most upvoted comments

There are no plans currently to support web components

See also #4075, #5054, #6203

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.

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.

I don’t know if this helps but I managed to find a way to move all of the Vuetify styles from <head> to shadow DOM.

Add style-loader to webpack config, with these options:

{
  test: /\.(sass|less|css|scss)$/,
  use: [
    ...
    { loader: "style-loader", options: { attributes: { class: "some-class" }, injectType: 'singletonStyleTag'} },
    ...
  ],
},

Then, in your main.js file or whatever it’s called, use javascript to move the styles to shadow DOM:

import Vue from "vue";
import Vuetify from "vuetify/lib";

vuetify.framework.theme.checkOrCreateStyleElement = function () {
  if (!this.styleEl) this.genStyleElement()
  return Boolean(this.styleEl)
}

const app = new Vue({
  Vuetify,
  ...
});

const shadowHost = document.querySelector('#shadowHost')
const shadowRoot = shadowHost.attachShadow({ mode: 'open' })

const { styleEl } = app.$vuetify.theme
styleEl.remove()
shadowRoot.appendChild(styleEl)

let styles = document.querySelectorAll('style.some-class')
shadowRoot.appendChild(styles[0])

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

Some parts from the code above are taken from @matthieusieben’s comment.

@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