diff --git a/.gitignore b/.gitignore index 23efb53..7df6d18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,5 @@ .DS_Store node_modules -/dist -dist # local env files .env.local diff --git a/dev/serve.vue b/dev/serve.vue index c696606..da57671 100644 --- a/dev/serve.vue +++ b/dev/serve.vue @@ -1,11 +1,28 @@ @@ -22,7 +39,7 @@ diff --git a/src/component/OptionalTeleport.vue b/src/component/OptionalTeleport.vue new file mode 100644 index 0000000..956b329 --- /dev/null +++ b/src/component/OptionalTeleport.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/component/Popper.vue b/src/component/Popper.vue index 9906af4..d13d3a3 100644 --- a/src/component/Popper.vue +++ b/src/component/Popper.vue @@ -1,33 +1,39 @@ @@ -42,9 +48,11 @@ watch, watchEffect, onMounted, + defineExpose, } from "vue"; - import { usePopper, useContent, useClickAway } from "@/composables"; + import { usePopper, useClickAway } from "@/composables"; import Arrow from "./Arrow.vue"; + import OptionalTeleport from "./OptionalTeleport.vue"; const emit = defineEmits(["open:popper", "close:popper"]); const slots = useSlots(); @@ -82,19 +90,26 @@ type: Boolean, default: false, }, + /** + * A custom matcher function to stop clickaway handler + */ + customClickAwayMatcher: { + type: Function, + default: null, + }, /** * Offset in pixels along the trigger element */ offsetSkid: { - type: String, - default: "0", + type: [Number, String], + default: 0, }, /** * Offset in pixels away from the trigger element */ offsetDistance: { - type: String, - default: "12", + type: [Number, String], + default: 12, }, /** * Trigger the popper on hover @@ -131,13 +146,6 @@ type: [Number, String], default: 0, }, - /** - * The z-index of the Popper. - */ - zIndex: { - type: [Number, String], - default: 9999, - }, /** * Display an arrow on the popper */ @@ -149,8 +157,8 @@ * Stop arrow from reaching the edge of the popper */ arrowPadding: { - type: String, - default: "0", + type: [Number, String], + default: 0, }, /** * If the Popper should be interactive, it will close when clicked/hovered if false @@ -173,12 +181,82 @@ type: String, default: null, }, + /** + * Controls the adaptive options of computeStyles modifier. [Boolean] + */ + adaptive: { + type: Boolean, + default: true, + }, + /** + * Controls the gpuAcceleration options of computeStyles modifier. [Boolean] + */ + gpuAcceleration: { + type: Boolean, + default: true, + }, + /** + * This describes the area that the element will be checked for overflow relative to. [String, Element] + */ + boundary: { + default: null, + }, + /** + * Applies virtual padding to the boundary. [Number] + */ + boundaryPadding: { + type: [Number, String], + default: 5, + }, + /** + * DOM node to render the content. [String] + */ + container: { + type: String, + default: null, + }, + /** + * Class for the trigger wrapper. [String, Object, Array] + */ + triggerWrapperClass: { + type: [String, Object, Array], + default: null, + }, + /** + * Style for the trigger wrapper. [String, Object, Array] + */ + triggerWrapperStyle: { + type: [String, Object, Array], + default: null, + }, + /** + * Stops propagation of the event on clicking the trigger element. [Boolean] + */ + triggerStopPropagation: { + type: Boolean, + default: false, + }, + /** + * Class for the content wrapper. [String, Object, Array] + */ + contentWrapperClass: { + type: [String, Object, Array], + default: null, + }, + /** + * Style for the content wrapper. [String, Object, Array] + */ + contentWrapperStyle: { + type: [String, Object, Array], + default: null, + }, }); const popperContainerNode = ref(null); const popperNode = ref(null); const triggerNode = ref(null); const modifiedIsOpen = ref(false); + const isMounted = ref(false); onMounted(() => { const children = slots.default(); @@ -188,6 +266,8 @@ `[Popper]: The component expects only one child element at its root. You passed ${children.length} child nodes.`, ); } + + isMounted.value = true; }); const { @@ -195,6 +275,7 @@ closeDelay, content, disableClickAway, + customClickAwayMatcher, disabled, interactive, locked, @@ -203,9 +284,15 @@ openDelay, placement, show, + adaptive, + gpuAcceleration, + boundary, + boundaryPadding, + container, + triggerStopPropagation, } = toRefs(props); - const { isOpen, open, close } = usePopper({ + const { isOpen, open, close, update } = usePopper({ arrowPadding, emit, locked, @@ -214,16 +301,16 @@ placement, popperNode, triggerNode, + boundary, + boundaryPadding, + adaptive, + gpuAcceleration, }); - const { hasContent } = useContent(slots, popperNode, content); - const manualMode = computed(() => show.value !== null); - const invalid = computed(() => disabled.value || !hasContent.value); - const shouldShowPopper = computed(() => isOpen.value && !invalid.value); - const enableClickAway = computed( - () => !disableClickAway.value && !manualMode.value, - ); + const shouldShowPopper = computed(() => isOpen.value && !disabled.value); + const enableClickAway = computed(() => !disableClickAway.value && !manualMode.value); + // Add an invisible border to keep the Popper open when hovering from the trigger into it const interactiveStyle = computed(() => interactive.value @@ -235,7 +322,7 @@ const closePopperDebounce = debounce(close, closeDelay.value); const openPopper = async () => { - if (invalid.value || manualMode.value) { + if (disabled.value || manualMode.value) { return; } @@ -252,16 +339,25 @@ closePopperDebounce(); }; + const updatePopper = update; + const togglePopper = () => { isOpen.value ? closePopper() : openPopper(); }; + const onTriggerClick = (e) => { + togglePopper(); + if (triggerStopPropagation.value) { + e.stopPropagation(); + } + }; + /** * If Popper is open, we automatically close it if it becomes * disabled or without content. */ - watch([hasContent, disabled], ([hasContent, disabled]) => { - if (isOpen.value && (!hasContent || disabled)) { + watch(disabled, (disabled) => { + if (isOpen.value && disabled) { close(); } }); @@ -295,15 +391,19 @@ */ watchEffect(() => { if (enableClickAway.value) { - useClickAway(popperContainerNode, closePopper); + useClickAway(popperContainerNode, popperNode, customClickAwayMatcher.value, closePopper); } }); + + defineExpose({ + openPopper, + closePopper, + togglePopper, + updatePopper, + })