client: refactor tooltip directive

Using the beforeUnmount hook should hopefully improve issues with
tooltips being left behind.
This commit is contained in:
Johann150 2022-12-22 14:12:25 +01:00
parent 52afff800a
commit ddf3e2c3db
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1

View file

@ -1,31 +1,83 @@
// TODO: useTooltip関数使うようにしたい // TODO: use the useTooltip function
// ただディレクティブ内でonUnmountedなどのcomposition api使えるのか不明
import { defineAsyncComponent, Directive, ref } from 'vue'; import { defineAsyncComponent, Directive, ref } from 'vue';
import { isTouchUsing } from '@/scripts/touch'; import { isTouchUsing } from '@/scripts/touch';
import { popup, alert } from '@/os'; import { popup, alert } from '@/os';
const start = isTouchUsing ? 'touchstart' : 'mouseover';
const end = isTouchUsing ? 'touchend' : 'mouseleave';
const delay = 100; const delay = 100;
export default { class TooltipDirective {
mounted(el: HTMLElement, binding) { public text: string | null;
const self = (el as any)._tooltipDirective_ = {} as any; private asMfm: boolean;
self.text = binding.value as string; private _close: null | () => void;
self._close = null; private showTimer: null | ReturnType<typeof window.setTimeout>;
private hideTimer: null | ReturnType<typeof window.setTimeout>;
constructor(binding) {
this.text = binding.value;
this.asMfm = binding.modifiers.mfm ?? false;
this._close = null;
this.showTimer = null;
this.hideTimer = null;
}
private close(): void {
if (this.hideTimer != null) return; // already closed or closing
// cancel any pending attempts to show
window.clearTimeout(self.showTimer);
self.showTimer = null; self.showTimer = null;
self.hideTimer = null;
self.checkTimer = null;
self.close = () => { self.hideTimer = window.setTimeout(() => {
if (self._close) { this._close?.();
window.clearInterval(self.checkTimer); this._close = null;
self._close(); }, delay);
self._close = null; },
}
}; public show(el): void {
if (!document.body.contains(el)) return;
if (this.text == null) return; // no content
if (this.showTimer != null) return; // already showing or going to show
// cancel any pending attempts to hide
window.clearTimeout(self.hideTimer);
self.hideTimer = null;
self.showTimer = window.setTimeout(() => {
const showing = ref(true);
popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
showing,
text: self.text,
asMfm: self.asMfm,
targetElement: el,
}, {}, 'closed');
self._close = () => {
showing.value = false;
};
}, delay);
},
}
/**
* Show a tooltip on mouseover. The content of the tooltip is the text
* provided as the value of this directive.
*
* Supported arguments:
* v-tooltip:dialog -> show text as a dialog on mousedown
*
* Supported modifiers:
* v-tooltip.mfm -> show tooltip content as MFM
*/
export default {
created(el: HTMLElement, binding) {
(el as any)._tooltipDirective_ = new TooltipDirective(binding);
},
mounted(el: HTMLElement, binding) {
const self = el._tooltipDirective_ as TooltipDirective;
if (binding.arg === 'dialog') { if (binding.arg === 'dialog') {
el.addEventListener('click', (ev) => { el.addEventListener('click', (ev) => {
@ -39,53 +91,20 @@ export default {
}); });
} }
self.show = () => { // add event listeners
if (!document.body.contains(el)) return; const start = isTouchUsing ? 'touchstart' : 'mouseover';
if (self._close) return; const end = isTouchUsing ? 'touchend' : 'mouseleave';
if (self.text == null) return; el.addEventListener(start, () => self.show(el), { passive: true });
el.addEventListener(end, () => self.close(), { passive: true });
const showing = ref(true); el.addEventListener('click', self.close());
popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), { el.addEventListener('selectstart', ev => ev.preventDefault());
showing,
text: self.text,
asMfm: binding.modifiers.mfm,
targetElement: el,
}, {}, 'closed');
self._close = () => {
showing.value = false;
};
};
el.addEventListener('selectstart', ev => {
ev.preventDefault();
});
el.addEventListener(start, () => {
window.clearTimeout(self.showTimer);
window.clearTimeout(self.hideTimer);
self.showTimer = window.setTimeout(self.show, delay);
}, { passive: true });
el.addEventListener(end, () => {
window.clearTimeout(self.showTimer);
window.clearTimeout(self.hideTimer);
self.hideTimer = window.setTimeout(self.close, delay);
}, { passive: true });
el.addEventListener('click', () => {
window.clearTimeout(self.showTimer);
self.close();
});
}, },
updated(el, binding) { beforeUpdate(el, binding) {
const self = el._tooltipDirective_; (el._tooltipDirective_ as TooltipDirective).text = binding.value as string;
self.text = binding.value as string; }
},
unmounted(el) { beforeUnmount(el) {
const self = el._tooltipDirective_; (el._tooltipDirective_ as TooltipDirective).close();
window.clearInterval(self.checkTimer);
}, },
} as Directive; } as Directive;