karol-f / vue-custom-element

Vue Custom Element - Web Components' Custom Elements for Vue.js
https://karol-f.github.io/vue-custom-element/
MIT License
1.97k stars 187 forks source link

Vuetify2 compatibility? #269

Closed DallyBoodil closed 9 months ago

DallyBoodil commented 1 year ago

As with vue-web-component-wrapper, I'm having the same problem with getting Vuetify to work when the custom element is using shadowDOM (thankfully with this package, ShadowDOM can be disabled).

This package works fine when it's not using shadowDOM but it's not a bulletproof solution due to CSS conflicts when importing the web component onto 3rd party websites using Vuetify, Tailwind, Bootstrap, etc.

I keep getting this console error but don't know whether it's anything to with this issue:

[Vuetify] Unable to locate target [data-app]

found in

---> <VDialog>
       <Payment> at src/views/Payment.vue
         <VApp>
           <App> at src/App.vue
             <Root>

Has anyone encountered the same issue and managed to get Vuetify to work with shadowDOM?

A working non-ShadomDom demo of my web component can be found here.

When ShadowDOM, is enabled, this is how it renders: https://ibb.co/0Y5tfjm

karol-f commented 1 year ago

Can you please prepare working CodeSandbox (https://codesandbox.io/s/vue) or GitHub repo with not working scenario? It would be easier to debug it.

karol-f commented 1 year ago

Please also look at this:

tmcdos commented 9 months ago

Here is how I use vue-custom-element with Vuetify 2:

/// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router.js';
import store from './store';
import i18n from './i18n';
import './lib/directives';
import './lib/filters';
import ajax from './lib/ajax';
import vuetify from './vuetify.js';
import vueCustomElement from 'vue-custom-element';

Vue.config.ignoredElements = ['event-analytics'];

Vue.use(vueCustomElement);

App.router = router;
App.store = store;
App.vuetify = vuetify;

Vue.customElement('event-analytics', App, {
  shadow: true,
  beforeCreateVueInstance(root)
  {
    const rootNode = root.el.getRootNode();

    if (rootNode instanceof ShadowRoot)
    {
      root.shadowRoot = rootNode;
    }
    else
    {
      root.shadowRoot = document.head;
    }
    root.i18n = i18n;
    root.vuetify = vuetify; // otherwise we lose this.$vuetify on hot-reload in development

    // Monkey patch querySelector to properly find root element - otherwise v-menu, v-tooltip and similar do not work
    const { querySelector } = document;
    document.querySelector = function(selector)
    {
      if (selector === '[data-app]') return root.shadowRoot.querySelector(selector);
      return querySelector.call(this, selector);
    };

    return root;
  },
});
<template>
<!-- src/App.vue -->
  <v-app>
    <v-main :data-route="$route.name">
      <transition name="fade" appear mode="out-in">
        <router-view />
      </transition>
    </v-main>
  </v-app>
</template>
<script>
export default
{
  name: 'App',
  props: // whatever you need to pass from the parent HTML page to the WebComponent of your App
  created()
  {
    // we need the Vuetify theme CSS variables to be inside the Shadow DOM - but Vuetify does not know about Shadow DOM, so we copy the styles manually
    const theme = document.querySelector('#vuetify-theme-stylesheet');
    if (theme)
    {
      const styleElem = document.createElement('style');
      styleElem.innerHTML = theme.innerHTML;
      this.$root.$options.shadowRoot.appendChild(styleElem);
    }
    // browser will not load web fonts in the Shadow DOM - only in the real DOM. But we still need the CSS styles in the ShadowDOM.
    const styleOpenSans = document.createElement('link');
    styleOpenSans.rel = 'stylesheet';
    styleOpenSans.href = 'https://fonts.googleapis.com/css?family=Open+Sans:400,700,900&display=swap';
    document.head.append(styleOpenSans);
    const styleMDI = document.createElement('link');
    styleMDI.rel = 'stylesheet';
    styleMDI.href = 'https://cdn.jsdelivr.net/npm/@mdi/font@5.9.55/css/materialdesignicons.min.css';
    document.head.append(styleMDI);

    this.$vuetify.lang.current = this.$i18n.locale;
  }
};
</script>

<style>
  @import "https://cdn.jsdelivr.net/npm/@mdi/font@5.9.55/css/materialdesignicons.min.css";
</style>

<style src="vuetify/dist/vuetify.min.css"></style>

<style lang="scss">
  .v-application
  {
    box-sizing: border-box; // really important !!!
    overflow-y: scroll;
    /* All browsers without overlaying scrollbars */
    -webkit-text-size-adjust: 100%;
    /* Prevent adjustments of font size after orientation changes in iOS */
    word-break: normal;
    -moz-tab-size: 4;
    tab-size: 4;
  }

  .fade-enter-active,
  .fade-leave-active
  {
    transition: all 0.15s cubic-bezier(0.55, 0, 0.1, 1);
  }

  .fade-enter,
  .fade-leave-active
  {
    opacity: 0;
    transform: translate(-2em, 0);
  }

  #app.v-application
  {
    overflow-y: visible;
  }

  .v-application .v-application--wrap
  {
    min-height: 0;
  }
</style>
karol-f commented 9 months ago

Thank you for sharing!