From b2c18decdc57b8d2cda77cca7821a608dd18ae6b Mon Sep 17 00:00:00 2001 From: Jiri Zbytovsky Date: Wed, 11 Dec 2024 15:02:16 +0100 Subject: [PATCH] fix(suite): HiddenPlaceholder flickering --- .../components/suite/HiddenPlaceholder.tsx | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/suite/src/components/suite/HiddenPlaceholder.tsx b/packages/suite/src/components/suite/HiddenPlaceholder.tsx index 5a62a264d31..94815377c09 100644 --- a/packages/suite/src/components/suite/HiddenPlaceholder.tsx +++ b/packages/suite/src/components/suite/HiddenPlaceholder.tsx @@ -1,4 +1,4 @@ -import { ReactNode, useLayoutEffect, useRef, useState } from 'react'; +import { MouseEventHandler, ReactNode, useLayoutEffect, useRef, useState } from 'react'; import styled, { css } from 'styled-components'; @@ -7,6 +7,11 @@ import { RedactNumbersContext } from '@suite-common/wallet-utils'; import { useSelector } from 'src/hooks/suite'; import { selectIsDiscreteModeActive } from 'src/reducers/wallet/settingsReducer'; +type MouseCoords = { clientX: number; clientY: number }; + +const PIXELS_TOLERANCE = 0.5; +const approxEqual = (a: number, b: number) => Math.abs(a - b) < PIXELS_TOLERANCE; + type WrapperProps = { $intensity: number; $discreetMode: boolean; @@ -67,6 +72,7 @@ export const HiddenPlaceholder = ({ const [automaticIntensity, setAutomaticIntensity] = useState(10); const [wrapperMinWidth, setWrapperMinWidth] = useState(undefined); const [isHovered, setIsHovered] = useState(false); + const [mouseEnteredAtCoords, setmouseEnteredAtCoords] = useState(null); const discreetMode = useSelector(selectIsDiscreteModeActive); const shouldRedactNumbers = discreetMode && !isHovered; @@ -85,23 +91,37 @@ export const HiddenPlaceholder = ({ // we only need to handle the case when revealed content is smaller than redacted, not vice versa. // in such case, onMouseEnter shrinks content, and it may immediately onMouseLeave even when cursor is still. - const onMouseEnter = () => { + const onMouseEnter: MouseEventHandler = ({ clientX, clientY }) => { setIsHovered(true); + setmouseEnteredAtCoords({ clientX, clientY }); if (ref?.current) { setWrapperMinWidth(ref.current.getBoundingClientRect().width); } }; - const onMouseLeave = () => { + const onMouseLeave: MouseEventHandler = ({ clientX, clientY }) => { + // prevent flickering that may happen when mouse cursor stands still exactly at the element edge + if ( + mouseEnteredAtCoords !== null && + approxEqual(clientX, mouseEnteredAtCoords.clientX) && + approxEqual(clientY, mouseEnteredAtCoords.clientY) + ) { + return; + } + setIsHovered(false); setWrapperMinWidth(undefined); }; + // edge case: user enters the element, and then leaves through the same pixel. onMouseLeave would then return + const onMouseMove = () => setmouseEnteredAtCoords(null); + const shouldEnforceMinWidth = discreetMode && !disableKeepingWidth; return (