/* eslint-disable @typescript-eslint/no-explicit-any */ import { arrow, computePosition, flip, offset, shift } from '@floating-ui/dom'; import { IPopoverOptions } from '@ibiz-template/runtime'; import { useNamespace } from '@ibiz-template/vue-util'; import { computed, CreateElement, defineComponent, onUnmounted, PropType, ref, VNode, } from 'vue'; import '@ibiz-template/theme/style/components/util/popover/popover.scss'; import { OverlayPopoverContainer } from '../overlay-popover-container/overlay-popover-container'; import { useUIStore } from '@/store'; /** * 计算飘窗显示 * * @author chitanda * @date 2022-11-08 21:11:18 * @param {HTMLElement} element * @param {HTMLElement} el * @param {HTMLElement} arrEl * @param {IPopoverOptions} opts * @return {*} {Promise<void>} */ async function computePos( element: HTMLElement, el: HTMLElement, arrEl: HTMLElement, opts: IPopoverOptions, ): Promise<void> { const middlewareArr = [offset(opts.offsetOpts || 6), flip(), shift()]; if (!opts.noArrow) { middlewareArr.push(arrow({ element: arrEl! })); } const options = await computePosition(element, el, { placement: opts.placement, strategy: 'absolute', middleware: middlewareArr, }); { const { x, y, placement, middlewareData } = options; const { style } = el; style.left = `${x}px`; style.top = `${y}px`; if (!opts.noArrow) { // 箭头位置 const { x: arrowX, y: arrowY } = middlewareData.arrow!; const staticSide: any = { top: 'bottom', right: 'left', bottom: 'top', left: 'right', }[placement.split('-')[0]]; Object.assign(arrEl.style, { left: arrowX != null ? `${arrowX}px` : '', top: arrowY != null ? `${arrowY}px` : '', right: '', bottom: '', [staticSide]: '-4px', }); } } } const AppPopoverComponent = defineComponent({ props: { opts: { type: Object as PropType<IPopoverOptions>, default: () => ({}), }, }, setup(props, ctx) { // 样式命名空间 const ns = useNamespace('popover'); // 是否显示 const isShow = ref(false); // 是否悬浮在内容区,当悬浮在内容区时 autoClose 禁用 const isHover = ref(false); // 跟 dom 元素 const el = ref<HTMLDivElement>(); // arrow dom 元素 const arrEl = ref<HTMLDivElement>(); // 是否可以自动关闭 const autoClose = computed<boolean>(() => { return props.opts.autoClose === true && isHover.value === false; }); const { zIndex } = useUIStore(); const popoverZIndex = zIndex.increment(); // 鼠标入飘窗内容区 function onMouseenter(e: MouseEvent): void { e.stopPropagation(); isHover.value = true; } // 鼠标出飘窗内容区 function onMouseleave(e: MouseEvent): void { e.stopPropagation(); isHover.value = false; } // 点击容器关闭飘窗 function dismiss(): void { zIndex.decrement(); ctx.emit('dismiss'); } // 组件销毁用于事件触发 function destroy(e: Event): void { e.stopPropagation(); if (autoClose.value) { dismiss(); } } function addEvents(): void { window.addEventListener('mousedown', destroy, { capture: true }); window.addEventListener('blur', destroy, { capture: true }); window.addEventListener('resize', destroy, { capture: true }); } function removeEvents(): void { window.removeEventListener('mousedown', destroy, { capture: true }); window.removeEventListener('blur', destroy, { capture: true }); window.removeEventListener('resize', destroy, { capture: true }); } addEvents(); onUnmounted(() => { removeEvents(); }); /** * 飘窗显示并计算位置 * * @author chitanda * @date 2022-11-09 12:11:04 * @param {HTMLElement} target * @return {*} {Promise<void>} */ async function present(target: HTMLElement): Promise<void> { isShow.value = true; await computePos(target, el.value!, arrEl.value!, props.opts); } return { ns, el, arrEl, isShow, isHover, present, dismiss, onMouseenter, onMouseleave, popoverZIndex, }; }, render() { return ( <div class={[ this.ns.b(), this.ns.is('show', this.isShow), this.ns.is('hover', this.isHover), ]} ref='el' onMouseenter={this.onMouseenter} onMouseleave={this.onMouseleave} style={{ [this.ns.cssVarBlockName('z-index')]: this.popoverZIndex }} > {!this.opts.noArrow && ( <div class={[this.ns.e('arrow')]} ref='arrEl'></div> )} {this.$slots.default} </div> ); }, }); /** * 创建飘窗 * * @author chitanda * @date 2022-11-09 15:11:08 * @export * @param {(_h: CreateElement) => VNode} render * @param {IPopoverOptions} [opts] * @return {*} {OverlayPopoverContainer} */ export function createPopover( render: (_h: CreateElement) => VNode, opts?: IPopoverOptions, ): OverlayPopoverContainer { return new OverlayPopoverContainer(AppPopoverComponent, render, opts); }