From e1cd7c94fb13f8e49667b17554d22ce8de627a2a Mon Sep 17 00:00:00 2001 From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 10 May 2025 07:58:26 +0900 Subject: refactor(frontend): use* 関数の格納場所のフォルダ名を composables に変更 (#16004) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(frontend): use* 関数の格納場所を正式名称(composables)に変更 * migrate * move useLoading --- packages/frontend/src/composables/use-tooltip.ts | 106 +++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 packages/frontend/src/composables/use-tooltip.ts (limited to 'packages/frontend/src/composables/use-tooltip.ts') diff --git a/packages/frontend/src/composables/use-tooltip.ts b/packages/frontend/src/composables/use-tooltip.ts new file mode 100644 index 0000000000..af76a3a1e8 --- /dev/null +++ b/packages/frontend/src/composables/use-tooltip.ts @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { ref, watch, onUnmounted } from 'vue'; +import type { Ref } from 'vue'; + +export function useTooltip( + elRef: Ref, + onShow: (showing: Ref) => void, + delay = 300, +): void { + let isHovering = false; + + // iOS(Androidも?)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、それを無視するためのフラグ + // 無視しないと、画面に触れてないのにツールチップが出たりし、ユーザビリティが損なわれる + // TODO: 一度でもタップすると二度とマウスでツールチップ出せなくなるのをどうにかする 定期的にfalseに戻すとか...? + let shouldIgnoreMouseover = false; + + let timeoutId: number; + + let changeShowingState: (() => void) | null; + + let autoHidingTimer; + + const open = () => { + close(); + if (!isHovering) return; + if (elRef.value == null) return; + const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el; + if (!window.document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため + + const showing = ref(true); + onShow(showing); + changeShowingState = () => { + showing.value = false; + }; + + autoHidingTimer = window.setInterval(() => { + if (elRef.value == null || !window.document.body.contains(elRef.value instanceof Element ? elRef.value : elRef.value.$el)) { + if (!isHovering) return; + isHovering = false; + window.clearTimeout(timeoutId); + close(); + window.clearInterval(autoHidingTimer); + } + }, 1000); + }; + + const close = () => { + if (changeShowingState != null) { + changeShowingState(); + changeShowingState = null; + } + }; + + const onMouseover = () => { + if (isHovering) return; + if (shouldIgnoreMouseover) return; + isHovering = true; + timeoutId = window.setTimeout(open, delay); + }; + + const onMouseleave = () => { + if (!isHovering) return; + isHovering = false; + window.clearTimeout(timeoutId); + window.clearInterval(autoHidingTimer); + close(); + }; + + const onTouchstart = () => { + shouldIgnoreMouseover = true; + if (isHovering) return; + isHovering = true; + timeoutId = window.setTimeout(open, delay); + }; + + const onTouchend = () => { + if (!isHovering) return; + isHovering = false; + window.clearTimeout(timeoutId); + window.clearInterval(autoHidingTimer); + close(); + }; + + const stop = watch(elRef, () => { + if (elRef.value) { + stop(); + const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el; + el.addEventListener('mouseover', onMouseover, { passive: true }); + el.addEventListener('mouseleave', onMouseleave, { passive: true }); + el.addEventListener('touchstart', onTouchstart, { passive: true }); + el.addEventListener('touchend', onTouchend, { passive: true }); + el.addEventListener('click', close, { passive: true }); + } + }, { + immediate: true, + flush: 'post', + }); + + onUnmounted(() => { + close(); + }); +} -- cgit v1.2.3-freya