Conversation
…s from delegated handlers The delegated mousemove handler hides the tooltip whenever no trigger matches, and since f5#859 it is also attached to the tooltip container. When the container is document.body, any mouse movement on the page hides a tooltip that Crosshair pinned via `forceShowAt`, and the follow-cursor fallback drags it to the pointer. Track ownership instead: `show()` marks the tooltip as externally controlled and `hide()` releases it, so the delegated handlers leave imperatively shown tooltips alone. Co-Authored-By: Claude Fable 5 <[email protected]>
Contributor
Author
|
This PR fixes the issue but there's some flickering that I don't like. I'll see if we can fix it Screen.Recording.2026-08-27.at.5.08.25.PM.movUpdate: Fixed in the second commit |
…ointer position With `forceShowAt` set, `shouldShow` still required the pointer to be within the Y range, so moving the mouse over the axis area (inside the SVG but outside the plot) hid the pinned crosshair and tooltip, and leaving the SVG re-showed them — visible as flickering. A pinned crosshair's visibility should only depend on the forced position being within the X range. Co-Authored-By: Claude Fable 5 <[email protected]>
rokotyan
force-pushed
the
fix/tooltip-external-control
branch
2 times, most recently
from
August 28, 2026 00:17
44326a9 to
5353b85
Compare
Collaborator
|
I still see the flickering issue Screen.Recording.2026-09-01.at.12.28.13.PM.mov |
lee00678
reviewed
Sep 1, 2026
|
|
||
| /** Hides the tooltip after `hideDelay` */ | ||
| public hide (): void { | ||
| this._isControlledExternally = false |
Collaborator
There was a problem hiding this comment.
This clears _isControlledExternally immediately, even though the actual _hide() DOM update can be deferred by config.hideDelay. If a user passed a Tooltip with hideDelay set into config.tooltip for Crosshair (default is undefined, so this doesn't affect the default/dev-example case), a mousemove during that delay window could very briefly re-trigger the followCursor fallback before the fade-out completes.
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Crosshair'sforceShowAtis broken: the pinned tooltip disappears as soon as the mouse moves anywhere on the page (repro: the Force Show At Position example inpackages/dev).The regression was introduced in #859. That PR added the tooltip container to the list of elements the Tooltip's delegated
mousemovehandler is attached to (to fix follow-cursor freezing over trigger gaps). But that handler has a "no trigger matched →hide()" fallback, and when the tooltip container isdocument.body, it now runs on every mouse movement on the page:tooltip.show()and never registers anytriggers, so no trigger ever matches.mousemovehandler re-shows the tooltip in the same frame, so the conflict stays invisible (as it always has — this hide/re-show race predates Component | Tooltip: Fix fade-in animation and follow-cursor freeze over trigger gaps #859).The new follow-cursor fallback from #859 also calls
place()with the raw cursor position on non-trigger mousemoves, yanking aforceShowAt-pinned tooltip toward the pointer (Crosshair force-setsfollowCursor: true).The underlying gap: the delegated handler assumes it exclusively owns show/hide based on
triggers, with no notion that a component like Crosshair is managing the tooltip imperatively.Fix
Tooltip — make ownership explicit.
show()(the imperative API — Crosshair is its only caller in the codebase) marks the tooltip as externally controlled;hide()releases it. While the flag is set, the delegatedmousemove/mouseleavehandlers neither hide the tooltip nor re-place it — the owning component decides.No Crosshair changes needed for this part: it already calls
tooltip.hide()whenever the crosshair should disappear (out of range, mouseout, wheel), which releases the flag. Trigger-driven tooltips never callshow(), so their behavior is untouched. As a side benefit, mouse-driven crosshair tooltips no longer get hidden and re-shown by the delegated handler on every mousemove.Crosshair — a pre-existing flicker in the same scenario: with
forceShowAtset,shouldShowstill required the pointer to be within the Y range, so moving the mouse over the axis area (inside the SVG but outside the plot) hid the pinned crosshair and tooltip, and leaving the SVG re-showed them. A pinned crosshair's visibility now depends only on the forced position being within the X range, not on where the pointer is.Verified
Force Show At Positiondev example: tooltip stays pinned while the mouse moves anywhere on the page, including over the axis area and across the container edge (no flicker).Simple Crosshairdev example (regression check): tooltip still follows the pointer over the chart and hides when the pointer leaves the SVG (via Crosshair's ownmouseout→hide()path).show()is never called on that path).🤖 Generated with Claude Code