How to properly use slot inside of vue js web component and apply styles

Refresh

March 2019

Views

58 time

1

I have come across an issue where the implementation of slots in a webcomponent is not functioning as expected. My understanding of Web Components, Custom Elements and Slots is that elements rendered in a slot should inherit their style from the document and not the Shadow DOM however the element in the slot is actually being added to the Shadow DOM and therefore ignoring the global styles. I have created the following example to illustrate the issue that I am having.

shared-ui

This is a Vue application that is compiled to web components using the cli (--target wc --name shared-ui ./src/components/*.vue)

CollapseComponent.vue
<template>
    <div :class="[$style.collapsableComponent]">
        <div :class="[$style.collapsableHeader]" @click="onHeaderClick" :title="title">
            <span>{{ title }}</span> 
        </div>
        <div :class="[$style.collapsableBody]" v-if="expanded">
            <slot name="body-content"></slot>
        </div>
    </div>
</template>

<script lang="ts">
    import { Vue, Component, Prop } from 'vue-property-decorator'

    @Component({})
    export default class CollapsableComponent extends Vue {
        @Prop({ default: "" })
        title!: string;

        @Prop({default: false})
        startExpanded!: boolean;

        private expanded: boolean = false;

        constructor() {
            super();
            this.expanded = this.startExpanded;
        }

        get isVisible(): boolean {
            return this.expanded;
        }

        onHeaderClick(): void {
            this.toggle();
        }

        public toggle(expand?: boolean): void {
            if(expand === undefined) {
                this.expanded = !this.expanded;
            }
            else {
                this.expanded = expand;
            }
            this.$emit(this.expanded? 'expand' : 'collapse');
        }

        public expand() {
            this.expanded = true;

        }

        public collapse() {
            this.expanded = false;
        }
    }
</script>

<style module>
    :host {
        display: block;
    }

    .collapsableComponent {
        background-color: white;
    }

    .collapsableHeader {
        border: 1px solid grey;
        background: grey;
        height: 35px;
        color: black;
        border-radius: 15px 15px 0 0;
        text-align: left;
        font-weight: bold;
        line-height: 35px;
        font-size: 0.9rem;
        padding-left: 1em;
    }

    .collapsableBody {
        border: 1px solid black;
        border-top: 0;
        border-radius: 0 0 10px 10px;
        padding: 1em;
    }
</style>

shared-ui-consumer

This is a vue application that imports the shared-ui web component using a standard script include file.

App.vue
<template>
  <div id="app">
    <shared-ui title="Test">
      <span class="testClass" slot="body-content">
        Here is some text
      </span>
    </shared-ui>
  </div>
</template>

<script lang="ts">
import 'vue'
import { Component, Vue } from 'vue-property-decorator';

@Component({ })
export default class App extends Vue {

}
</script>

<style lang="scss">
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.testClass{
  color: red;
}
</style>
main.ts
import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

// I needed to do this so the web component could reference Vue
(window as any).Vue = Vue;

new Vue({
  render: h => h(App),
}).$mount('#app');

In this example I would expect the content inside the container to have red text however because Vue is cloning the element into the Shadow DOM the .testClass style is being ignored and the text is rendered with a black fill.

How can I apply .testClass to the element inside of my web component?

0 answers