fix(client): better hover detection
This commit is contained in:
		
							parent
							
								
									bcf7530eef
								
							
						
					
					
						commit
						e804a299e0
					
				
					 4 changed files with 41 additions and 32 deletions
				
			
		|  | @ -19,10 +19,6 @@ | ||||||
| 				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction" | 				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction" | ||||||
| 				:custom-emojis="notification.note.emojis" | 				:custom-emojis="notification.note.emojis" | ||||||
| 				:no-style="true" | 				:no-style="true" | ||||||
| 				@touchstart.passive="onReactionMouseover" |  | ||||||
| 				@mouseover="onReactionMouseover" |  | ||||||
| 				@mouseleave="onReactionMouseleave" |  | ||||||
| 				@touchend="onReactionMouseleave" |  | ||||||
| 			/> | 			/> | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|  | @ -151,7 +147,7 @@ export default defineComponent({ | ||||||
| 			os.api('users/groups/invitations/reject', { invitationId: props.notification.invitation.id }); | 			os.api('users/groups/invitations/reject', { invitationId: props.notification.invitation.id }); | ||||||
| 		}; | 		}; | ||||||
| 
 | 
 | ||||||
| 		const { onMouseover: onReactionMouseover, onMouseleave: onReactionMouseleave } = useTooltip((showing) => { | 		useTooltip(reactionRef, (showing) => { | ||||||
| 			os.popup(XReactionTooltip, { | 			os.popup(XReactionTooltip, { | ||||||
| 				showing, | 				showing, | ||||||
| 				reaction: props.notification.reaction ? props.notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : props.notification.reaction, | 				reaction: props.notification.reaction ? props.notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : props.notification.reaction, | ||||||
|  | @ -170,8 +166,6 @@ export default defineComponent({ | ||||||
| 			rejectFollowRequest, | 			rejectFollowRequest, | ||||||
| 			acceptGroupInvitation, | 			acceptGroupInvitation, | ||||||
| 			rejectGroupInvitation, | 			rejectGroupInvitation, | ||||||
| 			onReactionMouseover, |  | ||||||
| 			onReactionMouseleave, |  | ||||||
| 			elRef, | 			elRef, | ||||||
| 			reactionRef, | 			reactionRef, | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
|  | @ -6,10 +6,6 @@ | ||||||
| 	class="hkzvhatu _button" | 	class="hkzvhatu _button" | ||||||
| 	:class="{ reacted: note.myReaction == reaction, canToggle }" | 	:class="{ reacted: note.myReaction == reaction, canToggle }" | ||||||
| 	@click="toggleReaction()" | 	@click="toggleReaction()" | ||||||
| 	@touchstart.passive="onMouseover" |  | ||||||
| 	@mouseover="onMouseover" |  | ||||||
| 	@mouseleave="onMouseleave" |  | ||||||
| 	@touchend="onMouseleave" |  | ||||||
| > | > | ||||||
| 	<XReactionIcon :reaction="reaction" :custom-emojis="note.emojis"/> | 	<XReactionIcon :reaction="reaction" :custom-emojis="note.emojis"/> | ||||||
| 	<span>{{ count }}</span> | 	<span>{{ count }}</span> | ||||||
|  | @ -90,7 +86,7 @@ export default defineComponent({ | ||||||
| 			if (!props.isInitial) anime(); | 			if (!props.isInitial) anime(); | ||||||
| 		}); | 		}); | ||||||
| 
 | 
 | ||||||
| 		const { onMouseover, onMouseleave } = useTooltip(async (showing) => { | 		useTooltip(buttonRef, async (showing) => { | ||||||
| 			const reactions = await os.api('notes/reactions', { | 			const reactions = await os.api('notes/reactions', { | ||||||
| 				noteId: props.note.id, | 				noteId: props.note.id, | ||||||
| 				type: props.reaction, | 				type: props.reaction, | ||||||
|  | @ -113,8 +109,6 @@ export default defineComponent({ | ||||||
| 			buttonRef, | 			buttonRef, | ||||||
| 			canToggle, | 			canToggle, | ||||||
| 			toggleReaction, | 			toggleReaction, | ||||||
| 			onMouseover, |  | ||||||
| 			onMouseleave, |  | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -3,10 +3,6 @@ | ||||||
| 	ref="buttonRef" | 	ref="buttonRef" | ||||||
| 	class="eddddedb _button canRenote" | 	class="eddddedb _button canRenote" | ||||||
| 	@click="renote()" | 	@click="renote()" | ||||||
| 	@touchstart.passive="onMouseover" |  | ||||||
| 	@mouseover="onMouseover" |  | ||||||
| 	@mouseleave="onMouseleave" |  | ||||||
| 	@touchend="onMouseleave" |  | ||||||
| > | > | ||||||
| 	<i class="fas fa-retweet"></i> | 	<i class="fas fa-retweet"></i> | ||||||
| 	<p v-if="count > 0" class="count">{{ count }}</p> | 	<p v-if="count > 0" class="count">{{ count }}</p> | ||||||
|  | @ -42,7 +38,7 @@ export default defineComponent({ | ||||||
| 
 | 
 | ||||||
| 		const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id); | 		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', { | 			const renotes = await os.api('notes/renotes', { | ||||||
| 				noteId: props.note.id, | 				noteId: props.note.id, | ||||||
| 				limit: 11 | 				limit: 11 | ||||||
|  | @ -87,8 +83,6 @@ export default defineComponent({ | ||||||
| 			buttonRef, | 			buttonRef, | ||||||
| 			canRenote, | 			canRenote, | ||||||
| 			renote, | 			renote, | ||||||
| 			onMouseover, |  | ||||||
| 			onMouseleave, |  | ||||||
| 		}; | 		}; | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -1,8 +1,16 @@ | ||||||
| import { Ref, ref } from 'vue'; | import { Ref, ref, watch } from 'vue'; | ||||||
| import { isScreenTouching, isTouchUsing } from './touch'; |  | ||||||
| 
 | 
 | ||||||
| export function useTooltip(onShow: (showing: Ref<boolean>) => void) { | export function useTooltip( | ||||||
|  | 	elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>, | ||||||
|  | 	onShow: (showing: Ref<boolean>) => void, | ||||||
|  | ): void { | ||||||
| 	let isHovering = false; | 	let isHovering = false; | ||||||
|  | 
 | ||||||
|  | 	// iOS(Androidも?)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、それを無視するためのフラグ
 | ||||||
|  | 	// 無視しないと、画面に触れてないのにツールチップが出たりし、ユーザビリティが損なわれる
 | ||||||
|  | 	// TODO: 一度でもタップすると二度とマウスでツールチップ出せなくなるのをどうにかする 定期的にfalseに戻すとか...?
 | ||||||
|  | 	let shouldIgnoreMouseover = false; | ||||||
|  | 
 | ||||||
| 	let timeoutId: number; | 	let timeoutId: number; | ||||||
| 
 | 
 | ||||||
| 	let changeShowingState: (() => void) | null; | 	let changeShowingState: (() => void) | null; | ||||||
|  | @ -11,11 +19,6 @@ export function useTooltip(onShow: (showing: Ref<boolean>) => void) { | ||||||
| 		close(); | 		close(); | ||||||
| 		if (!isHovering) return; | 		if (!isHovering) return; | ||||||
| 
 | 
 | ||||||
| 		// iOS(Androidも?)では、要素をタップした直後に(おせっかいで)mouseoverイベントを発火させたりするため、その対策
 |  | ||||||
| 		// これが無いと、画面に触れてないのにツールチップが出たりしてしまう
 |  | ||||||
| 		// TODO: タッチとマウス両方使っている環境では、マウス操作でツールチップ出せなくなるのをどうにかする
 |  | ||||||
| 		if (isTouchUsing && !isScreenTouching) return; |  | ||||||
| 
 |  | ||||||
| 		const showing = ref(true); | 		const showing = ref(true); | ||||||
| 		onShow(showing); | 		onShow(showing); | ||||||
| 		changeShowingState = () => { | 		changeShowingState = () => { | ||||||
|  | @ -32,6 +35,7 @@ export function useTooltip(onShow: (showing: Ref<boolean>) => void) { | ||||||
| 
 | 
 | ||||||
| 	const onMouseover = () => { | 	const onMouseover = () => { | ||||||
| 		if (isHovering) return; | 		if (isHovering) return; | ||||||
|  | 		if (shouldIgnoreMouseover) return; | ||||||
| 		isHovering = true; | 		isHovering = true; | ||||||
| 		timeoutId = window.setTimeout(open, 300); | 		timeoutId = window.setTimeout(open, 300); | ||||||
| 	}; | 	}; | ||||||
|  | @ -43,8 +47,31 @@ export function useTooltip(onShow: (showing: Ref<boolean>) => void) { | ||||||
| 		close(); | 		close(); | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	return { | 	const onTouchstart = () => { | ||||||
| 		onMouseover, | 		shouldIgnoreMouseover = true; | ||||||
| 		onMouseleave, | 		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', | ||||||
|  | 	}); | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue