Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added 'teleport' prop to allow teleporting popper #35

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions dev/serve.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@
<div>This is the content</div>
</template>
</Popper>
<Popper arrow teleport="body">
<button>Click this</button>
<template #content>
<div>This is the teleported content</div>
</template>
</Popper>
<Popper hover arrow>
<button>Hover this</button>
<template #content>
<div>This is the content</div>
</template>
</Popper>
<Popper hover arrow teleport="body">
<button>Hover this</button>
<template #content>
<div>This is the teleported content</div>
</template>
</Popper>
</div>
</template>

Expand Down
1 change: 1 addition & 0 deletions docs/guide/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
| `content` | `null` | If your content is just a simple string, you can pass it as a prop |
| `show` | `null` | Control the Popper **manually**, other events (click, hover) are ignored if this is set to `true/false` |
| `zIndex` | `9999` | The z-index of the Popper |
| `teleport` | `null` | Teleport Popper element to selector specified |

## Events

Expand Down
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

67 changes: 50 additions & 17 deletions src/component/Popper.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<template>
<div
:style="interactiveStyle"
@mouseleave="hover && closePopper()"
v-click-away="{ handler: closePopper, enabled: enableClickAway }"
@mouseleave="hover && mouseLeave()"
v-click-away="{ handler: closePopper, enabled: enableClickAway, ignore: ['popperNode'] }"
>
<div
ref="triggerNode"
@mouseover="hover && openPopper()"
@mouseover="hover && mouseEnter()"
@click="togglePopper"
@focus="openPopper"
@keyup.esc="closePopper"
Expand All @@ -15,20 +15,24 @@
<!-- The default slot to trigger the popper -->
<slot />
</div>
<Transition name="fade">
<div
@click="!interactive && closePopper()"
v-show="shouldShowPopper"
:class="['popper', shouldShowPopper ? 'inline-block' : null]"
ref="popperNode"
>
<!-- A slot for the popper content -->
<slot name="content" :close="close" :isOpen="modifiedIsOpen">
{{ content }}
</slot>
<div v-if="arrow" id="arrow" data-popper-arrow></div>
</div>
</Transition>
<PopperTeleportWrapper :teleport="teleport">
<Transition name="fade">
<div
@mouseover="hover && teleport && mouseEnter()"
@mouseleave="hover && teleport && mouseLeave()"
@click="!interactive && closePopper()"
v-show="shouldShowPopper"
:class="['popper', shouldShowPopper ? 'inline-block' : null]"
ref="popperNode"
>
<!-- A slot for the popper content -->
<slot name="content" :close="close" :isOpen="modifiedIsOpen">
{{ content }}
</slot>
<div v-if="arrow" id="arrow" data-popper-arrow></div>
</div>
</Transition>
</PopperTeleportWrapper>
</div>
</template>

Expand All @@ -44,6 +48,7 @@
} from "vue";
import { usePopper, useContent } from "@/composables";
import clickAway from "@/directives";
import PopperTeleportWrapper from './PopperTeleportWrapper.vue'

/* Delay execution for a set amount of milliseconds */
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
Expand All @@ -57,6 +62,9 @@
directives: {
clickAway,
},
components: {
PopperTeleportWrapper,
},
props: {
/**
* Preferred placement (the "auto" placements will choose the side with most space.)
Expand Down Expand Up @@ -175,6 +183,31 @@
type: String,
default: null,
},
/**
* Teleport popper element to selector
*/
teleport: {
type: String,
default: null,
},
},
data() {
return {
isHovered: false,
}
},
methods: {
mouseEnter() {
this.isHovered = true;
this.openPopper();
},
async mouseLeave() {
this.isHovered = false;
await delay(50);
if(!this.isHovered) {
this.closePopper();
}
},
},
setup(props, { slots, emit }) {
const children = slots.default();
Expand Down
25 changes: 25 additions & 0 deletions src/component/PopperTeleportWrapper.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<template>
<teleport v-if="teleport" :to="teleport">
<slot />
</teleport>
<slot v-else />
</template>

<script>
import { defineComponent } from "vue";
/**
* The Popper Teleport Wrapper component.
*/
export default /*#__PURE__*/ defineComponent({
name: "PopperTeleportWrapper",
props: {
/**
* Teleport popper element to selector
*/
teleport: {
type: String,
default: null,
},
},
});
</script>
18 changes: 14 additions & 4 deletions src/directives/click-away.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
export default {
beforeMount: (el, binding) => {
el.clickAwayEvent = event => {
if(!binding.value.enabled) {
return;
}

const allowedEls = [el];
if(Array.isArray(binding.value.ignore)) {
binding.value.ignore.forEach((refName) => {
allowedEls.push(binding.instance.$refs[refName]);
});
}
const safeClickedEl = allowedEls.find((element) =>
element != null && (element == event.target || element.contains(event.target))
);
// Clicked outside of the element and its children
if (
!(el == event.target || el.contains(event.target)) &&
binding.value.enabled
) {
if (!safeClickedEl) {
// Call the provided method
binding.value.handler();
}
Expand Down