UNPKG

kui-vue

Version:

A lightweight desktop UI component library suitable for Vue.js 2.

281 lines (274 loc) 7.64 kB
import { defineComponent, ref, // cloneVNode, watch, // Transition, nextTick, provide, onMounted, onBeforeMount, } from "vue"; // import cloneVNode from '../utils/clone'; import { cloneVNode } from "../utils/vue"; import { withInstall /* cloneVNode*/ } from "../utils/vue"; import { setPlacement } from "../utils/placement"; import transfer from "../directives/transfer"; import resize from "../directives/resize"; import { getChildren } from "../utils/vnode"; const Dropdown = defineComponent({ name: "Dropdown", directives: { transfer, resize, }, props: { dark: Boolean, trigger: { type: String, default: "hover", validator(value) { return ["hover", "click", "contextmenu"].indexOf(value) >= 0; }, }, transfer: { type: Boolean, default: true }, disabled: Boolean, arrow: { type: Boolean, default: false }, show: Boolean, placement: { validator(value) { return ( [ "top", "top-left", "top-right", "bottom", "bottom-left", "bottom-right", ].indexOf(value) >= 0 ); }, default: "bottom-left", }, target: Object, }, setup(ps, { slots, emit, attrs, listeners }) { const visible = ref(ps.show); const refSelection = ref(null); const currentPlacement = ref(ps.placement); const transOrigin = ref("bottom"); const refPopper = ref(); const left = ref(0); const top = ref(0); const rendered = ref(false); const showTimer = ref(false); provide("dropdown", true); onMounted(() => { if (ps.show) { toggle(true); } }); onBeforeMount(() => { document.removeEventListener("click", outsideClick); }); const clearPopTimer = () => clearTimeout(showTimer.value); provide("clearPopTimer", clearPopTimer); watch( () => ps.placement, (v) => { currentPlacement.value = v; updatePosition(); } ); watch( () => ps.show, (v) => { toggle(v); } ); const outsideClick = (e) => { const ctx = refSelection.value?.$el || refSelection.value; if (!refPopper.value) return; if ( (!refPopper.value.contains(e.target) && ctx && !ctx.contains(e.target)) || (ps.trigger == "contextmenu" && !refPopper.value.contains(e.target)) ) { visible.value = false; } }; const updatePosition = (e) => { const position = e ? { x: e.clientX, y: e.clientY } : null; nextTick(() => { setPlacement({ refSelection, position, refPopper, currentPlacement, transOrigin, top, left, }); }); }; const toggle = (open, e) => { if (open) { if (!rendered.value) { rendered.value = true; document.addEventListener("click", outsideClick); nextTick(() => { visible.value = true; emit("update:visible", true); updatePosition(e); }); } else { visible.value = true; emit("update:visible", true); updatePosition(e); } } else { visible.value = false; emit("update:visible", false); } }; const hidePopper = () => { visible.value = false; }; provide("dropdown-menu-selected", hidePopper); const clickEvent = (e) => { if (ps.disabled) { return; } if (ps.trigger == "click") { toggle(true); } }; const mouseLeaveEvent = (e) => { if (ps.disabled) { return; } if (ps.trigger == "hover") { showTimer.value = setTimeout(() => { toggle(false); }, 300); } }; const mouseEnterEvent = (e) => { if (ps.disabled) { return; } if (ps.trigger == "hover") { clearTimeout(showTimer.value); toggle(true); } }; const contextmenuEvent = (e) => { if (ps.disabled) { return; } if (ps.trigger == "contextmenu") { e.preventDefault(); toggle(true, e); } }; provide("dropdown-trigger-in", mouseEnterEvent); provide("dropdown-trigger-out", mouseLeaveEvent); return () => { const props = { ref: refPopper, style: { left: `${left.value}px`, top: `${top.value}px`, transformOrigin: transOrigin.value, }, attrs: { "k-placement": currentPlacement.value }, class: ["k-dropdown", { "k-dropdown-has-arrow": ps.arrow }], on: { click: (e) => { toggle(false); }, mouseenter: () => { clearTimeout(showTimer.value); }, mouseleave: () => { if (ps.trigger == "hover") { showTimer.value = setTimeout(() => { toggle(false); }, 300); } }, }, // onClick: (e) => { for 3 // toggle(false); // }, // onMouseenter: () => { // clearTimeout(showTimer.value); // }, // onMouseleave: () => { // if (ps.trigger == "hover") { // showTimer.value = setTimeout(() => { // toggle(false); // }, 300); // } // }, }; const overlay = rendered.value && slots.overlay ? ( <transition name="k-dropdown"> <div v-transfer={true} v-resize={updatePosition} v-show={visible.value} {...props} > <div class={`k-dropdown-content`}> <div class={`k-dropdown-body`}>{slots.overlay?.()}</div> {ps.arrow ? ( <div class={`k-dropdown-arrow`}> <svg style={{ fill: "currentcolor" }} viewBox="0 0 24 8"> <path d="M24,0.97087 L24,1.97087 C20,1.97087 18.5,2.97087 16.5,4.97087 C14.5,6.97087 14,7.97087 12,7.97087 C10,7.97087 9.5,6.97087 7.5,4.97087 C5.5,2.97087 4,1.97087 0,1.97087 L0,0.97087 L24,0.97087 Z" id="ot" /> <path d="M24,0 L24,1 C20.032328,1 18.1576594,1.985435 16.1576594,3.985435 C14.1576594,5.985435 13.3847825,7 12,7 C10.6152175,7 9.81306952,5.985435 7.81306952,3.985435 C5.81306952,1.985435 4.0114261,1 0,1 L0,0 L24,0 Z" id="in" stroke="currentcolor" /> </svg> </div> ) : null} </div> </div> </transition> ) : null; let nodes = getChildren(slots.default?.()); const pp = ps.target ? {} : { on: { click: clickEvent, mouseenter: mouseEnterEvent, mouseleave: mouseLeaveEvent, contextmenu: contextmenuEvent, }, // onClick: clickEvent, for 3 // onMouseenter: mouseEnterEvent, // onMouseleave: mouseLeaveEvent, // onContextmenu: contextmenuEvent, }; const ctxNode = cloneVNode( nodes.length == 1 ? nodes[0] : <span>{nodes}</span>, { ref: refSelection, ...pp, }, true, overlay ); return ctxNode; }; }, }); export default withInstall(Dropdown);