import { defineAsyncComponent, Directive, ref } from "vue"; import autobind from "autobind-decorator"; import { popup } from "@/os"; export class UserPreview { private el; private user; private showTimer; private hideTimer; private checkTimer; private promise; constructor(el, user) { this.el = el; this.user = user; this.attach(); } @autobind private show() { if (!document.body.contains(this.el)) return; if (this.promise) return; const showing = ref(true); popup( defineAsyncComponent(() => import("@/components/MkUserPreview.vue")), { showing, q: this.user, source: this.el, }, { mouseover: () => { window.clearTimeout(this.hideTimer); }, mouseleave: () => { window.clearTimeout(this.showTimer); this.hideTimer = window.setTimeout(this.close, 500); }, }, "closed", ); this.promise = { cancel: () => { showing.value = false; }, }; this.checkTimer = window.setInterval(() => { if (!document.body.contains(this.el)) { window.clearTimeout(this.showTimer); window.clearTimeout(this.hideTimer); this.close(); } }, 1000); } @autobind private close() { if (this.promise) { window.clearInterval(this.checkTimer); this.promise.cancel(); this.promise = null; } } @autobind private onMouseover() { window.clearTimeout(this.showTimer); window.clearTimeout(this.hideTimer); this.showTimer = window.setTimeout(this.show, 500); } @autobind private onMouseleave() { window.clearTimeout(this.showTimer); window.clearTimeout(this.hideTimer); this.hideTimer = window.setTimeout(this.close, 500); } @autobind private onClick() { window.clearTimeout(this.showTimer); this.close(); } @autobind public attach() { this.el.addEventListener("mouseover", this.onMouseover); this.el.addEventListener("mouseleave", this.onMouseleave); this.el.addEventListener("click", this.onClick); } @autobind public detach() { this.el.removeEventListener("mouseover", this.onMouseover); this.el.removeEventListener("mouseleave", this.onMouseleave); this.el.removeEventListener("click", this.onClick); window.clearInterval(this.checkTimer); } } export default { mounted(el: HTMLElement, binding, vn) { if (binding.value == null) return; // TODO: 新たにプロパティを作るのをやめMapを使う // ただメモリ的には↓の方が省メモリかもしれないので検討中 const self = ((el as any)._userPreviewDirective_ = {} as any); self.preview = new UserPreview(el, binding.value); }, unmounted(el, binding, vn) { if (binding.value == null) return; const self = el._userPreviewDirective_; self.preview.detach(); }, } as Directive;