vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.7k stars 33.67k forks source link

Is it possible to emit event from component inside slot #4332

Closed purepear closed 6 years ago

purepear commented 7 years ago

Vue.js version

2.1.3

Reproduction Link

https://jsfiddle.net/x8a3vvk2/8/

Events emitted from component inside slot are not received. $emit('testevent') is not working but $parent.test() works.

<script src="https://unpkg.com/vue@2.1.3/dist/vue.js"></script>

<div id="app" >
  <parent>
    <child></child>
   </parent>
</div>
Vue.component('parent', {
  template: `<div><slot @testevent="test"></slot></div>`,
  methods: {
    test () {
      alert('Test ok')
    }
  }
})

Vue.component('child', {
  template: `<button @click="$emit('testevent')">I'm slot</button>`
  //template: `<button @click="$parent.test()">I'm slot</button>`
})

new Vue({
  el: '#app',
})
yyx990803 commented 7 years ago

You cannot listen to events on <slot>. It can end up rendering anything: text, plain element, multiple nodes... the behavior will be unpredictable.

It seems you are trying to make a slot container communicate with a slot child - in most cases this means the two components are coupled by-design, so you can do something like this.$parent.$emit(...) from the child, and listen to that event in the parent with this.$on(...).

I am still open to ideas on improving the ways slot parent and child can communicate, but events on <slot> doesn't really sound like a right direction to me.

guykatz commented 7 years ago

not trying to wake this up from the dead but... image I want a wrapper custom component that shows a loading spinner and has a slot (slot container). the spinner should be removed when an event is emitted from a component that occupies the slot (slot child). the parent's template looks something like:

<div>
  <slot></slot>  
    <div class="spinner active"></div>
</div>

the child's code looks something like:

    mounted: function () {
      this.$emit('loadStatusChange');
     }
  },
  template:'<span>some template txt<span>'

the usage is something like this:

  <vue-loading-wrapper>
      <vue-child></vue-child>
  </vue-loading-wrapper>

in order for the slot child to fulfill the parent's interface, they should emit an event to turn the spinner to inactive. One can argue that using $parent.emit with $on (per suggestion @yyx990803 above) complicates the issue as you need to take into consideration the life cycle of components into this. if, for example: IT the slot child emits the event on mount than the slot container should register for it in earlier phases (not mount) because the child is mounted before the parent container and so in such a case, the event will be fired but missed by the parent. Now, if you passed the hurdle above, you find yourself in an odd situation because you want to remove the spinner on the slot container usually after it has been mounted (thats the whole point). so now you need some code trickery on the parent in order to see if the 'clear spinner' event was received AND the the parent component is mounted.

long story short, in order to get the full slot power I do believe some sort of a mechanism should be introduced. I am no JS expect by any means, this is just from the short experience I have with vue. your thoughts are welcomed

Justineo commented 6 years ago

@guykatz

In your case:

<vue-loading-wrapper>
  <vue-child></vue-child>
</vue-loading-wrapper>

The state of vue-child should be driven by the data (data, props, computed) of the context component which holds this template. You can also bind that to vue-loading-wrapper. Even if vue-child loads data for its own, you can still do:

<vue-loading-wrapper :loading="childLoading">
  <vue-child @load="childLoading = false"></vue-child>
</vue-loading-wrapper>
TomS- commented 6 years ago

I'm having a lot of trouble with this. I can see that this will be a high use case, or at least I thought it would be.

Personally I have a card ui that carries a background, inside the card can be any content so it uses <slot></slot> in some cases it's just a background with text, however in others I would like to use a link that changes the background. The link is also a component. So the HTML looks like:

 <vue-card header="<?=$page->title ?>" background="<?=$page->background->size(500, 500)->url ?>">
    <?php foreach($page->link as $link): ?>
        <vue-link link="<?=$link->url ?>" @mouseover.native="background = '<?=$link->background->size(500, 500)->url ?>'"></vue-link>
    <?php endforeach; ?>
 </vue-card>

According to the documentation this should work and it would do if I could pass JSON over and make the HTML above into a component using v-for, however, because I need to access the CMS resize-image functions I can't do this. @mouseover.native targets the new Vue() instance rather than the <vue-card> component.

In this case $emit or even $parent.$emit will not work because it is firing on the new Vue() instance. So it seems impossible and I'll have to avoid using Vue for this element. I'll have to use native javascript on created I guess. But it isn't exactly the Vue way.

Edit: Made a new component just to add native javascript on mount:

mounted: function() {
        var vm = this;

        [].forEach.call(this.$children[0].$children, function(n) {
            n.$el.addEventListener("mouseenter", function(e) {
                if(e.target.hasAttribute("data-background")) {
                    vm.activeBackground = e.target.getAttribute("data-background");
                }
            });
            n.$el.addEventListener("mouseleave", function(e) {
                vm.activeBackground = vm.background;
            });
        });
    }

It's very hacky and not "very Vue" at all... Wish there was a way to bubble up an event inside a slot...

Danonk commented 6 years ago

Holy shit @TomS- , such mixing php and Vue should be punishable by death! Your code is horribly unmaintainable.

TomS- commented 6 years ago

@Danonk I hardly think "punishable by death" is fair. Not that I have to justify myself to you, I'm using a PHP CMS that doesn't have a REST API and I want to use Vue. Luckily you don't have to deal with the code I'm writing.

ghost commented 6 years ago

I'm facing with problem with too

Currently I am creating a Modal in which will contain any Form components

My modal contain tag

<div>
   ...
   <slot></slot>
</div>

I call Modal component like below and I place LoginForm component inside Modal component

<modal>
   <loginForm/>
</modal>

Inside the LoginForm component, I place an anchor tag to close the modal

<div>
   ...
   <a @click="close">LOG IN</a>
</div>

<script>
   ...
   methods: {
      close () {
         this.$emit('close');
      }
   }
</script>

However, this button is not working. Whenever I click the button, I get this error:

Uncaught TypeError: Cannot read property '$emit' of null

Now I'm stuck at this.

I have already tried the suggestion from @Justineo but cannot make it work, any idea?

drachum commented 6 years ago

Hi!

I arrived at the exact same problem that @dodatrmit3618835, and I think that in cases like this, listening to events on slot could be a good solution.

theianjohnson commented 6 years ago

👍

I could also use this, my use case is trying to use one or more date picker components within a data table component. I'd like the date picker to be able to emit an event with their key/value pair for the data table to update itself with.

<datatable>
    <datepicker key="ordered_at" name="Ordered"></datepicker>
    <datepicker key="paid_on" name="Paid"></datepicker>
    // etc
</datatable>

Then these date pickers get slotted into their appropriate places in the data table component. If the slot can render anything why restrict event listeners?

kingschnulli commented 6 years ago

@dodatrmit3618835 I had exactly the same problem, scoped slots can you help in this case:

Modal (parent) has to get a scoped slot with a closeHandler property, this should be some kind of method to close your modal, please adapt to your needs - my usecase uses uiv for a bootstrap modal:

<template>
  <div>
    <Btn
      type="primary"
      block
      @click="open=true">{{ buttonText }}</Btn>
    <Modal
      v-model="open"
      :title="title">
      <slot
        v-if="open"
        :closeHandler="closeHandler"/>
    </Modal>
  </div>
</template>

<script>
import { Modal, Btn } from 'uiv'

export default {
  name: 'ModalButton',
  components: {
    Modal,
    Btn
  },
  props: {
    buttonText: String,
    title: String
  },
  data () {
    return {
      open: false
    }
  },
  methods: {
    closeHandler: function () {
      this.open = false
    }
  }
}
</script>

Your form (or whatever inner container) has to dispatch an event you can handle from the outside (close event in your case, mine is called productSelected) - use the component as following now:

<ModalButton button-text="My fancy button">
          <template slot-scope="{closeHandler}">
            <ProductList @productSelected="closeHandler"/>
          </template>
        </ModalButton>

This will allow you to wire up the parent/child components without any unneeded coupling.

limistah commented 6 years ago

This is my solution (similar to @aggrosoft 's):


<!-- Will be top level after the containing <div> -->
<grand-parent>
  <slot slot="parent"></slot>
</grand-parent>
<!-- Should be in a <grand-parent> -->
<parent>
  <!-- 1. Actually owns patChild method, does some magic but needs to know from a slot/child -->
  <!-- patChild (...receives data) { ...Magical!!! } -->
  <!-- 5. Receives the even through method call. -->
  <slot name="child" :patChild="pathChild">
    Expects a child
  </slot>
</parent>
<!-- Should be in a <parent> -->
<child @click="handleClick">
  <!--  2. Takes the method that needs data through props -->
  Accepts patChild as a prop (function)
  <!-- 3. Event actually happens here (a slot) -->
  <!-- 4. handleClick () { ...some magic; this.patChild(...some data)} -->
</child>

<!-- Composed structure -->
<div>
  <grand-parent>
    <parent slot="parent">
      <child slot="child" slot-scope="{pathChild}" :pathChild="pathChild"></child>
    </parent>
  </grand-parent>
</div>
ghost commented 6 years ago

It's really amazing that many solutions have been given by many developers

I wanna give my solution, too

Like what I mentioned above, I have several components to handle the Modal which listed below

Modal component: Modal.vue Form with Background component: FormBackground.vue Login Form content component: Login.vue Navbar component: Navbar.vue - which is the highest parent

Inside Navbar component, I called Modal with "closeModal" methods and Login component

<Modal
   v-show="isModalVisible"
   @close="closeModal"
>
   <Login />
</Modal>

methods: {
   closeModal() {
      this.isModalVisible = false;
   }
}

Inside the Modal component, I have <slot> tag, which will contain anything (form, paragraph etc.) which need Modal effect. This component will listen to closeModal methods inside Login component (different from this one in Navbar component) on Created and trigger to @close inside Navbar component

<div>
  <slot></slot>
</div>

created () {
   this.$on('closeModal', function(message) {
      this.$emit('close')
   })
}

Inside the Login component, there is a Button which contains the click method to trigger the closeModal inisde Modal component

<a class="btn btn-block btn-success btn-custom" @click="$parent.$emit('closeModal')">
   LOG IN
</a>

It can be summarized like this:

Modal component listen to @click inside Login component; then, Modal component trigger to @close inside Navbar component

And this works like a charm

For the solution of @aggrosoft , I have not used the slot-scope before so I cannot say if my method is better than your method but I will try it in another Modal (or something else) then report the result here soon

reneolivo commented 5 years ago

@TomS- I also found myself in a similar situation some years ago with a different framework. I suggest you export the data that you need as JSON inside a script tag:

<script>
window.someGlobalVariable = <?php json_encode($somedata); ?>
</script>
<script src="/dist/my-nice-vue-dist.js"></script>
jko206 commented 5 years ago

from the child, and listen to that event in the parent with this.$on(...)

Where would this.$on(...) go?

drachum commented 5 years ago

from the child, and listen to that event in the parent with this.$on(...)

Where would this.$on(...) go?

Hey @jaiko86 one way is to register this event when parent component is mounted, like:

// Parent.vue
...
mounted(){
   this.$on("child-event", this.handleChildEvent);
},
...
florin1693 commented 5 years ago

you can use named slots and do something like: `

`

kemaltatar commented 5 years ago
<slot name="form"
  :saveHandler="values => handleSave(values)"
  :cancelHandler="handleCancel"
/>
<template v-slot:form="{saveHandler, cancelHandler}">
  <ExampleForm @save="saveHandler" @cancel="cancelHandler" />
</template>

Is this hacky way of doing this a really bad idea ?

florin1693 commented 5 years ago
Pe miercuri, 31 iulie 2019, 10:51:18 EEST, Kemal Tatar <notifications@github.com> a scris:  

<slot name="form" :saveHandler="values => handleSave(values)" :cancelHandler="handleCancel" />

Is this hacky way of doing this a really bad idea ?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

bradley-varol commented 5 years ago

Here's a solution that I use for triggering a method which involves changing the value of a watched prop:

OuterComponent:

<template>
  <slot />
</template>
<script>
export default {
  props: {
    doSomethingTrigger: {
      type: Boolean,
      default: false,
    },
  },
  watch: {
    // we watch the boolean prop and trigger our method when it changes
    doSomethingTrigger(oldVal, newVal) {
      oldVal !== newVal && this.doSomething();
    },
  },
  methods: {
    doSomething() {
      // this is what you want to trigger from your slot
    }
  },
}
</script>

InnerComponent:

<template>
  <OuterComponent :do-something-trigger="doSomethingTrigger">
    <button @click="doSomething" />
  </OuterComponent>
</template>
<script>
export default {
  data() {
    return {
      doSomethingTrigger: false,
    };
  },
  methods: {
    doSomething() {
      // we change the value of the boolean which is passed
      // to our OuterComponent
      this.doSomethingTrigger = !this.doSomethingTrigger;
    },
  },
}
</script>
oookoook commented 4 years ago

Just another solution similar to @aggrosoft and @limistah:

Basically, you can adopt an approach I encountered in the Vuetify framework for the v-tooltip component: https://vuetifyjs.com/en/components/tooltips

Code looks like this:

 <v-tooltip bottom>
      <template v-slot:activator="{ on }">
        <v-btn color="primary" dark v-on="on">Button</v-btn>
      </template>
      <span>Tooltip</span>
 </v-tooltip>

As you can see, there is an event handler "on" defined in the v-tooltip component that is called when an event occurs in the slot. Yes, you have to maintain the binding of the event handler and the event trigger in the topmost parent component, but what's important - You don't have to define the event handler method in the topmost parent, but it can be defined in the component that contains the slot.

This approach uses the scoped slots functionality: https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots (and also the abbreviated syntax)

My code looks like this:

DataProvider.vue

<template>
  <div>
  <slot :loading="loading" :items="items" :total="total" :update="update">
    {{ JSON.stringify(items) }}
  </slot>
  </div>
</template>
<script>

export default {
  name: 'DataProvider',
  components: {
  },
  props: { 'table': String },
  data () {
    return {
      loading: false
    }
  },
  computed: {
    items() {
      return this.$store.getters[this.table];
    },
    total() {
      return this.$store.getters[`${this.table}Total`];
    },
  },
  watch: {

  },
  methods: {
    update(params) {
      /* LONG UNIMPORTANT EVENT HANDLER USING VUEX */
    }
  },
  created () {

  },
  mounted () {
    this.update();
  }
}

Traits.vue

<template>
  <data-provider table="traits" v-slot="i">
    <traits-table :items="i.items" :loading="i.loading" :total="i.total" @update="i.update"/>
  </data-provider>
</template>

As you can see, I pass the update method to the slot component as a parameter, then I bind it to the evnet on the component in the slot in the parent of the DataProvider and it works.

I hope this helps somebody.

JustusW commented 4 years ago

Just another usecase that would be immensely simpler if this was possible:

<template>
  <div class="zoom" @wheel="onwheel">
    <slot></slot>
  </div>
</template>

Now the wheel event gives you positional information relative to a) the element the event was registered to and b) the element the event originated from. If there are multiple layers of components below there is no way to get the information relative to the actual zooming target (which by definition has to be a single css transformable html element, which also happens to be a valid target for events). The only solution I can think of for my particular problem is basically to cobble together the whole stack of positional information until I'm back at the zoom element which is hella inefficient and prone to break. That or basically create a way to register an event on a slot because vue doesn't have that option.

Inventsable commented 4 years ago

Just another usecase that would be immensely simpler if this was possible:

<template>
  <div class="zoom" @wheel="onwheel">
    <slot></slot>
  </div>
</template>

This does work for simple @click handlers. I wanted to prompt a file picker on click of a <slot> and thought this would be difficult given this thread and initial googling, but found that the solution can really be as simple as:

Parent

<File-Picker>
  <div class="clickme">Click me</div>
</File-Picker>

File-Picker Component

<template>
  <div class="file-picker-wrapper">
    <input
      style="display:none;"
      ref="filepicker"
      type="file"
      accept="image/png, image/jpeg"
    />
    <div @click="openFilePicker" v-if="$slots.default.length">
      <slot />
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    openFilePicker() {
      this.$refs.filepicker.click();
    },
  },
};
</script>

Still +1 on slot event registration, but in case someone comes across this thread for something simple like me the answer might also be simple.

christianechevarria commented 4 years ago

Kind of breaking the meta here I assume, but what if there was a way to just have the markup that goes in a slot reference the "execution context" in which it was declared.

For example, I have a card component that looks something like this:

<template>
  <div class="blah blah styles">
    ...
      ...
        <slot name="headingArea">
          <p>Heading content goes here</p>
        </slot>
        <slot name="contentArea">
          <p>Content goes here</p>
        </slot>
      ...
    ...
  </div>
</template>

And inside it I'm putting arbitrary content:

<template>
  <card-with-heading v-for="(thing, key) in things" :key="key">
    <template slot="headingArea">
      <p>Some Heading Markup</p>
    </template>
    <template slot="contentArea">
      <span @click="addNew('thing')"></span>
    </template>
  </card-with-heading>
</template>

When I write @click the most intuitive thing is for it to reference the methods object in the single-file component that it was declared in.

We could cite a bunch of reasons why this is not a great idea, but the more re-usable and composable you make your markup the harder it is to pass things "up" or keep track of events.

In my particular use-case this component is inside yet another component so when the click event fires: vue, I'm assuming, tries to find "addNew" in the methods of the card-with-heading component.

I've read some of the approaches here, but the drawback I can see is that if we start defining every single method we might need inside a slotted component then its value as an abstract low-assumption component keeps diminishing.

It would be nice if you could tell vue "look for the click handler in this context":

<template>
  <card-with-heading v-for="(thing, key) in things" :key="key">
    <template slot="headingArea">
      <p>Some Heading Markup</p>
    </template>
    <template slot="contentArea">
      <span @execution-context:click="addNew('thing')"></span>
    </template>
  </card-with-heading>
</template>

Then no matter how many levels down this markup happens to be in nested components it's not a problem for me to just define it relative to the context that is intuitive to me: the file where it is declared

This was just a quick thought, I definitely welcome feedback on whether I'm missing some mechanic that would make this irrelevant.

I have a feeling this would be very difficult to actually implement anyway, but food for thought I guess

Edit: re-reading this kind of reminds me of the concept of currying, what's the currying equivalent for vue components?

syed-haroon commented 4 years ago

Check scoped slot. Assuming your carousel component has fnNext and fnClose methods:

Carousel template:

<div class="carousel">
  <div class="slides" ref="slides">
    <slot name="slide-ctrls" :events="{ fnNext, fnClose }"></slot>
  </div> 
  <footer>
    <!-- Other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

Carousel example usage:

<my-carousel>
  <template slot="slide-ctrls" slot-scope="{ events: { fnNext, fnClose } }">
    <div class="slide">
      <button @click="fnNext">Next</button>
    </div>

    <div class="slide">
      <button @click="fnClose">Close</button>
    </div>
  </template>
</my-carousel>

OR, use v-slot (much cleaner and latest way of doing things):

<my-carousel>
  <template v-slot:slide-ctrls="{ events: { fnNext, fnClose } }">
    <div class="slide">
      <button @click="fnNext">Next</button>
    </div>

    <div class="slide">
      <button @click="fnClose">Close</button>
    </div>
  </template>
</my-carousel>

Just in case if you like to see much expanded form of code instead of es6, though this seems bit confusing but this shows you where and how things are passed/used.

<div class="carousel">
  <div class="slides" ref="slides">
    <slot name="slide-ctrls" :events="{ atClickNext: fnNext, atClickClose: fnClose }"></slot>
  </div> 
  <footer>
    <!-- Other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

Carousel example usage:

<my-carousel>
  <template v-slot:slide-ctrls="{ events: { atClickNext: handleClickNext, atClickClose: handleClickClose } }">
    <div class="slide">
      <button @click="handleClickNext">Next</button>
    </div>

    <div class="slide">
      <button @click="handleClickClose">Close</button>
    </div>
  </template>
</my-carousel>

Answered the same here: https://stackoverflow.com/a/63671053/1292050

olfek commented 1 year ago

Anyone know if this is possible in Vue 3 ?

I have this with the proper emit setup code, but it doesn't work.

<slot @finish="toggle"></slot>
zayoboyo commented 1 year ago

@olfek No, this won't work, ChatGPT told me it'll work but after asking whether ChatGpt is right in this case on VueLand discord server, I was told this is not possible and ChatGPT is wrong.

Screenshot from 2023-02-04 23-31-24

Screenshot from 2023-02-04 23-53-53

olfek commented 1 year ago

Okay I did it with custom non Vue event listeners.

Why do limitations like this even exist 😞 - Let us "users" wield all the power, and decide how to use it, don't decide for us 😒

In the slotted component:

ev.currentTarget?.dispatchEvent(new Event("finish", { bubbles: true }));

On a div containing <slot></slot>:

:ref="(el) => addFinishListener(el)"

Finally, implementation of addFinishListener

function addFinishListener(el : Element | ComponentPublicInstance | null) : void
{
    if (el instanceof Element)
    {
        if (el.hasAttribute("finish"))
        {
            return;
        }

        el.setAttribute('finish', "true");
        el.addEventListener("finish", (ev)=>
        {
            ev.stopPropagation();
            toggle();
        });
    }
}
olfek commented 1 year ago

Could you please add a Vue way of doing the above please @yyx990803 , thank you 🙂

monadstack commented 1 year ago

up for vue3 example.

monadstack commented 1 year ago

Check scoped slot. Assuming your carousel component has fnNext and fnClose methods:

Carousel template:

<div class="carousel">
  <div class="slides" ref="slides">
    <slot name="slide-ctrls" :events="{ fnNext, fnClose }"></slot>
  </div> 
  <footer>
    <!-- Other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

Carousel example usage:

<my-carousel>
  <template slot="slide-ctrls" slot-scope="{ events: { fnNext, fnClose } }">
    <div class="slide">
      <button @click="fnNext">Next</button>
    </div>

    <div class="slide">
      <button @click="fnClose">Close</button>
    </div>
  </template>
</my-carousel>

OR, use v-slot (much cleaner and latest way of doing things):

<my-carousel>
  <template v-slot:slide-ctrls="{ events: { fnNext, fnClose } }">
    <div class="slide">
      <button @click="fnNext">Next</button>
    </div>

    <div class="slide">
      <button @click="fnClose">Close</button>
    </div>
  </template>
</my-carousel>

Just in case if you like to see much expanded form of code instead of es6, though this seems bit confusing but this shows you where and how things are passed/used.

<div class="carousel">
  <div class="slides" ref="slides">
    <slot name="slide-ctrls" :events="{ atClickNext: fnNext, atClickClose: fnClose }"></slot>
  </div> 
  <footer>
    <!-- Other carousel controls like arrows, indicators etc go here -->
  </footer>
</div>

Carousel example usage:

<my-carousel>
  <template v-slot:slide-ctrls="{ events: { atClickNext: handleClickNext, atClickClose: handleClickClose } }">
    <div class="slide">
      <button @click="handleClickNext">Next</button>
    </div>

    <div class="slide">
      <button @click="handleClickClose">Close</button>
    </div>
  </template>
</my-carousel>

Answered the same here: https://stackoverflow.com/a/63671053/1292050

I came from non-vue background and found this offensive, do u have an example in https://codepen.io/ or in similar web?

monadstack commented 1 year ago

In vue3, I solve this issue by using provide('key', val) and inject('key') which imported from 'vue', both value and function can be provided and used, great success. Like React Context behavior.

olfek commented 1 year ago

Emit does not work on <slot></slot> but it does work on <RouterView />.

This behavior is inconsistent. Both of these things host unknown children.

You cannot listen to events on <slot>. It can end up rendering anything: text, plain element, multiple nodes... the behavior will be unpredictable.

...

<RouterView /> can "end up rendering anything" too, but emit works on it and it feels right.

FYI - this is regarding Vue 3.

I think I'll create a new issue.

smylmrz commented 1 month ago

Not sure if we are having the same issue. So I needed to listen for an emit from a slot.

This is the parent component that I need to listen to the emit:

<slot :open="openImageModal" />

<MediaGroupEventHandler>
          <template #default="{ open }">
              <MediaGroup
                  v-if="photos.length"
                  :files="photos"
                  type="image"
                  @open="open"
              />
          </template>
      </MediaGroupEventHandler>

Hope it helps

rti-todd commented 1 week ago

I am not sure if this will help anyone, but since it was tough to find a solution for what I was doing, I am going to post this in a few places in hopes it helps anyone else..

I needed to send an emit from a slot, but at least in my situation, it was not working as it was a router-view from the main App component, in App, this simple solution works great, although it took me hours to figure out:

<LayoutWrapper v-slot:default="{ toggleDrawer }"> <router-view @toggleDrawer="toggleDrawer" :key="$route.fullPath" /> </LayoutWrapper>