How to binding events on slots without break layout of contents?

322 views Asked by At

I was trying to make a contextmenu component based on slots, which should be used like

<template>
<Contextmenu>
 <template #contextmenu>
  <!--customized contextmenu here-->
 <template>
 <template #default>
  <!--contents here-->
 <template>
<Contextmenu>
<template>

When right clicking on default slot, contextmenu slot would appear, and clicking on contextmenu would emit an event.

Inside the component it's like

<template>
  <div class="contextmenu-wrapper" v-if="showContextMenu">
    <slot name="contextmenu"></slot>
  </div>
  <div class="content-wrapper" @contextmenu.prevent.stop="onRightclickContent">
    <slot name="default"></slot>
  </div>
</template>

div.contextmenu-wrapper is fixed, and onRightclickContent will set it's top and left to put it in the right place. On component mounted a event listener will be registered to close the context menu after clicking outside it.

Things work fine but when I try this:

<template>
  <div class="wrapper">
    <ContextMenu>
      <template #contextmenu>
        <div class="contextmenu"></div>
      </template>
      <div class="inner"></div>
      <div class="inner"></div>
      <div class="inner inner3"></div>
    </ContextMenu>
  </div>
</template>

<style>
.wrapper {
  display: flex;
  justify-content: space-evenly;
}

.inner {
  width: 250px;
  height: 100px;
  border: 1px solid #ccc;
  background-color: bisque;
}

.inner3 {
  flex-grow: 1;
}

.content {
  max-width: 100px;
  background-color: #3498db;
}
.contextmenu {
  width: 100px;
  height: 100px;
  background-color: azure;
}
</style>

The problem is the div.content-wrapper blocks the flex layout. But without it how can I listen to the right click event on content?

A solution will be like this: wrap the whole component with a div, so classes will fall through to it, then layout it in the parent component:

// ContextMenu.vue
<template>
<div class="wrapper" @contextmenu.prevent.stop="onRightclickContent">
  <div class="contextmenu-wrapper" v-if="showContextMenu">
    <slot name="contextmenu"></slot>
  </div>
  <slot name="default"></slot>
</div>
</template>

// parent component
<template>
  <div class="wrapper">
    <ContextMenu v-for="i in 3" class="inner">
      <template #contextmenu>
        <div class="contextmenu"></div>
      </template>
      <div class="content"></div>
    </ContextMenu>
  </div>
</template>

But in this way there will be three same context menus, and it will be messy with complex content and bringing troubles to users. Please help me out of a way.

1

There are 1 answers

0
BowenDean On

Problem solved.

The problem is, I shouldn't try to affect slot contents in the parent component. So instead of binding event on slot#content while keep its layout, I let the component itself become the flex container:

// ContextMenu.vue
<template>
<div @contextmenu.prevent.stop="onRightclickContent">
  <div class="contextmenu-wrapper" v-if="showContextMenu">
    <slot name="contextmenu"></slot>
  </div>
  <slot name="default"></slot>
</div>
</template>

// parent component
<template>
  <!--no another wrapper div here!-->
    <ContextMenu class="wrapper">
      <template #contextmenu>
        <div class="contextmenu"></div>
      </template>
      <!--will be correctly layout now-->
      <div class="inner"></div>
      <div class="inner"></div>
      <div class="inner"></div>
    </ContextMenu>
</template>

<style>
.wrapper{
  display: flex;
}
</style>

class="wrapper" will fall through to root div in ContextMenu then it becomes the flex container.