From e804a299e068afcbfb2fe36cc429b9a502a7b2d9 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 5 Dec 2021 20:01:52 +0900 Subject: [PATCH] fix(client): better hover detection --- .../client/src/components/notification.vue | 8 +-- .../components/reactions-viewer.reaction.vue | 8 +-- .../client/src/components/renote-button.vue | 8 +-- packages/client/src/scripts/use-tooltip.ts | 49 ++++++++++++++----- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue index 15d36f5a64..37a88edc64 100644 --- a/packages/client/src/components/notification.vue +++ b/packages/client/src/components/notification.vue @@ -19,10 +19,6 @@ :reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction" :custom-emojis="notification.note.emojis" :no-style="true" - @touchstart.passive="onReactionMouseover" - @mouseover="onReactionMouseover" - @mouseleave="onReactionMouseleave" - @touchend="onReactionMouseleave" /> @@ -151,7 +147,7 @@ export default defineComponent({ os.api('users/groups/invitations/reject', { invitationId: props.notification.invitation.id }); }; - const { onMouseover: onReactionMouseover, onMouseleave: onReactionMouseleave } = useTooltip((showing) => { + useTooltip(reactionRef, (showing) => { os.popup(XReactionTooltip, { showing, reaction: props.notification.reaction ? props.notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : props.notification.reaction, @@ -170,8 +166,6 @@ export default defineComponent({ rejectFollowRequest, acceptGroupInvitation, rejectGroupInvitation, - onReactionMouseover, - onReactionMouseleave, elRef, reactionRef, }; diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue index 44c6e98093..a1de99f018 100644 --- a/packages/client/src/components/reactions-viewer.reaction.vue +++ b/packages/client/src/components/reactions-viewer.reaction.vue @@ -6,10 +6,6 @@ class="hkzvhatu _button" :class="{ reacted: note.myReaction == reaction, canToggle }" @click="toggleReaction()" - @touchstart.passive="onMouseover" - @mouseover="onMouseover" - @mouseleave="onMouseleave" - @touchend="onMouseleave" > {{ count }} @@ -90,7 +86,7 @@ export default defineComponent({ if (!props.isInitial) anime(); }); - const { onMouseover, onMouseleave } = useTooltip(async (showing) => { + useTooltip(buttonRef, async (showing) => { const reactions = await os.api('notes/reactions', { noteId: props.note.id, type: props.reaction, @@ -113,8 +109,6 @@ export default defineComponent({ buttonRef, canToggle, toggleReaction, - onMouseover, - onMouseleave, }; }, }); diff --git a/packages/client/src/components/renote-button.vue b/packages/client/src/components/renote-button.vue index 280283ec67..446686de10 100644 --- a/packages/client/src/components/renote-button.vue +++ b/packages/client/src/components/renote-button.vue @@ -3,10 +3,6 @@ ref="buttonRef" class="eddddedb _button canRenote" @click="renote()" - @touchstart.passive="onMouseover" - @mouseover="onMouseover" - @mouseleave="onMouseleave" - @touchend="onMouseleave" >

{{ count }}

@@ -42,7 +38,7 @@ export default defineComponent({ const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); - const { onMouseover, onMouseleave } = useTooltip(async (showing) => { + useTooltip(buttonRef, async (showing) => { const renotes = await os.api('notes/renotes', { noteId: props.note.id, limit: 11 @@ -87,8 +83,6 @@ export default defineComponent({ buttonRef, canRenote, renote, - onMouseover, - onMouseleave, }; }, }); diff --git a/packages/client/src/scripts/use-tooltip.ts b/packages/client/src/scripts/use-tooltip.ts index f72dcb162d..0df4baca7b 100644 --- a/packages/client/src/scripts/use-tooltip.ts +++ b/packages/client/src/scripts/use-tooltip.ts @@ -1,8 +1,16 @@ -import { Ref, ref } from 'vue'; -import { isScreenTouching, isTouchUsing } from './touch'; +import { Ref, ref, watch } from 'vue'; -export function useTooltip(onShow: (showing: Ref) => void) { +export function useTooltip( + elRef: Ref, + onShow: (showing: Ref) => void, +): void { let isHovering = false; + + // iOS(Androidも?)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、それを無視するためのフラグ + // 無視しないと、画面に触れてないのにツールチップが出たりし、ユーザビリティが損なわれる + // TODO: 一度でもタップすると二度とマウスでツールチップ出せなくなるのをどうにかする 定期的にfalseに戻すとか...? + let shouldIgnoreMouseover = false; + let timeoutId: number; let changeShowingState: (() => void) | null; @@ -11,11 +19,6 @@ export function useTooltip(onShow: (showing: Ref) => void) { close(); if (!isHovering) return; - // iOS(Androidも?)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、その対策 - // これが無いと、画面に触れてないのにツールチップが出たりしてしまう - // TODO: タッチとマウス両方使っている環境では、マウス操作でツールチップ出せなくなるのをどうにかする - if (isTouchUsing && !isScreenTouching) return; - const showing = ref(true); onShow(showing); changeShowingState = () => { @@ -32,6 +35,7 @@ export function useTooltip(onShow: (showing: Ref) => void) { const onMouseover = () => { if (isHovering) return; + if (shouldIgnoreMouseover) return; isHovering = true; timeoutId = window.setTimeout(open, 300); }; @@ -43,8 +47,31 @@ export function useTooltip(onShow: (showing: Ref) => void) { close(); }; - return { - onMouseover, - onMouseleave, + const onTouchstart = () => { + shouldIgnoreMouseover = true; + if (isHovering) return; + isHovering = true; + timeoutId = window.setTimeout(open, 300); }; + + const onTouchend = () => { + if (!isHovering) return; + isHovering = false; + window.clearTimeout(timeoutId); + 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 }); + } + }, { + immediate: true, + flush: 'post', + }); }