vuetifyjs / vuetify

🐉 Vue Component Framework
https://vuetifyjs.com
MIT License
39.84k stars 6.96k forks source link

[Bug Report] Weird behaviour inside shadow DOM #7622

Closed ivanov-wai closed 3 years ago

ivanov-wai commented 5 years ago

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:

Possible solutions:

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

KaelWD commented 5 years ago

There are no plans currently to support web components

See also #4075, #5054, #6203

ivanov-wai commented 5 years ago

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

KaelWD commented 5 years ago

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.

dwelch2344 commented 4 years ago

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.

dwelch2344 commented 4 years ago

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?

TheInvoker commented 4 years ago

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

dwelch2344 commented 4 years ago

@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

matthieusieben commented 4 years ago

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.

matthieusieben commented 4 years ago

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.

jggc commented 4 years ago

@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.

matthieusieben commented 4 years ago

@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)

matthieusieben commented 4 years ago

PR https://github.com/vuetifyjs/vuetify/pull/13053 fixes the "Cannot focus for text fields" issue.

bluec0re commented 3 years ago

Any progress on this?

guyschlider commented 3 years ago

@matthieusieben did you guys ended using some kind of a patch to have multiple vuetify instances (in different shadow-doms) on the same page? I guess patching querySelector is the problematic part, right?

KaelWD commented 3 years ago

This is fixed for text fields but not menus. Menus can use attach as a workaround.

matthieusieben commented 3 years ago

@guyschlider in my case, we are running a full Vue instance inside a chrome extension. This is quite convenient because web extension have a local "copy" of the DOM. This means that the monkey patching of document.querySelector only affects the JS of the extension itself.

There are more problematic parts than the document.querySelector though. For example; Vuetify's theme engine will generate one style sheet per vuetify instance. And the generated CSS is not "namespaced" to the v-app. So running multiplie themes will be an issue as well.

matthieusieben commented 3 years ago

Note that with the attachedRoot too introduced in https://github.com/vuetifyjs/vuetify/commit/dc19b82e2b9178ddf553b42a981208d6f2d811ac#diff-5bbdbd8c956db1e85f2f5845e151d3d1bd46e2bda71994cecfc579bec5c3de71R1-R24 , it should be easy to patch some of the other problematic parts of the code (including the attach issue).

Malachi-M commented 3 years ago

@KaelWD

Using Vuetify@2.4.6. A standalone Text-Field focuses as expected within the shadow DOM.

The Autocomplete (internal Text-Field) within a shadow DOM fails to focus or display the menu when focused. Auto-focus initially applies focus as expected.

Test implementation: https://codepen.io/MalachiMart/pen/PobVEdo

Has anyone else experienced this since version 2.4.5?

Darkside73 commented 3 years ago

@Malachi-M Autocomplete does not work. Menu with attach workaround appears but closes after interaction with date picker inside it (despite :close-on-content-click="false")

danielCristeaa commented 2 years ago

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.

amirtbi commented 1 year ago

Has the issue of not recognizing vuetify options inside of the web component been solved in the new version of Vuetify 3?, I've implemented the vuetify for a web component created by Vue3, but my options don't work correctly for web component. This is the link to my project:vue-web-component