From 2a23ab2ca5d979d0785552eadb43589baebab2c0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:50:20 +0000 Subject: [PATCH 1/4] fix: make tooltip interactive - allow mouse to enter tooltip to select/copy text The mouseout handler on [data-fsdocs-tip] trigger elements was hiding the tooltip whenever the mouse left the trigger, even if it was moving INTO the tooltip popover. This made it impossible to select or copy text from the tooltip. Fixes: 1. Trigger mouseout handler now checks if relatedTarget is inside the tooltip element, and skips hiding in that case. 2. New mouseout handler on .fsdocs-tip[popover] hides the tooltip when the mouse leaves the tooltip itself (unless it returns to the trigger). 3. CSS adds cursor: text and user-select: text to the open tooltip so users have a visual cue that text is selectable. Closes #949 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- RELEASE_NOTES.md | 1 + docs/content/fsdocs-default.css | 2 ++ docs/content/fsdocs-tips.js | 18 +++++++++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 72bd4ec10..ad7845d03 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -3,6 +3,7 @@ ## [Unreleased] ### Fixed +* Fix tooltip not being interactive: moving the mouse from a code token into its tooltip now keeps the tooltip open, allowing users to select and copy the tooltip text. [#949](https://github.com/fsprojects/FSharp.Formatting/issues/949) * Add regression test confirming that types whose name matches their enclosing namespace are correctly included in generated API docs. [#944](https://github.com/fsprojects/FSharp.Formatting/issues/944) * Fix crash (`failwith "tbd - IndirectImage"`) when `Markdown.ToMd` is called on a document containing reference-style images with bracket syntax. The indirect image is now serialised as `![alt](url)` when the reference is resolved, or in bracket notation when it is not. [#1094](https://github.com/fsprojects/FSharp.Formatting/pull/1094) * Fix `Markdown.ToMd` serialising italic spans with asterisks incorrectly as bold spans. [#1102](https://github.com/fsprojects/FSharp.Formatting/pull/1102) diff --git a/docs/content/fsdocs-default.css b/docs/content/fsdocs-default.css index 5e2c1261e..d7ff2089f 100644 --- a/docs/content/fsdocs-default.css +++ b/docs/content/fsdocs-default.css @@ -1012,6 +1012,8 @@ div.fsdocs-tip:popover-open { position: fixed; inset: unset; animation: fsdocs-tip-fade-in 120ms ease-out; + cursor: text; + user-select: text; } [data-fsdocs-tip] { diff --git a/docs/content/fsdocs-tips.js b/docs/content/fsdocs-tips.js index 7bb111290..fdf258d62 100644 --- a/docs/content/fsdocs-tips.js +++ b/docs/content/fsdocs-tips.js @@ -64,10 +64,26 @@ document.addEventListener('mouseout', function (evt) { // Only hide when the mouse has left the trigger element entirely if (target.contains(evt.relatedTarget)) return; const name = target.dataset.fsdocsTip; - const unique = parseInt(target.dataset.fsdocsTipUnique, 10); + // Don't hide if the mouse is moving into the tooltip itself (user wants to select/copy text) + const el = document.getElementById(name); + if (el && el.contains(evt.relatedTarget)) return; hideTip(name); }); +// Hide the tooltip when the mouse leaves the tooltip element itself +document.addEventListener('mouseout', function (evt) { + const tip = evt.target.closest('.fsdocs-tip[popover]'); + if (!tip) return; + // Stay open while the mouse remains inside the tooltip + if (tip.contains(evt.relatedTarget)) return; + // Stay open if the mouse returns to the trigger element + const trigger = document.querySelector(`[data-fsdocs-tip="${tip.id}"]`); + if (trigger && trigger.contains(evt.relatedTarget)) return; + try { tip.hidePopover(); } catch (_) { } + currentTip = null; + currentTipElement = null; +}); + function Clipboard_CopyTo(value) { if (navigator.clipboard) { navigator.clipboard.writeText(value); From ef866505b0ec49c1003733714632bdf9f3d6dbd4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 1 Apr 2026 10:50:23 +0000 Subject: [PATCH 2/4] ci: trigger checks From 93635e2ed2d5b2ccd5422f37f963ca3bedaad823 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 06:50:46 +0000 Subject: [PATCH 3/4] fix: add 150ms hide delay so mouse can cross gap between trigger and tooltip Previously, the 20px offset between cursor and tooltip created a dead zone. When the mouse left the trigger into this gap, the tooltip hid immediately because relatedTarget was not the tooltip element. A short delay gives the mouse time to reach the tooltip before hiding fires. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/content/fsdocs-tips.js | 41 +++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/docs/content/fsdocs-tips.js b/docs/content/fsdocs-tips.js index fdf258d62..427716ed5 100644 --- a/docs/content/fsdocs-tips.js +++ b/docs/content/fsdocs-tips.js @@ -1,7 +1,16 @@ let currentTip = null; let currentTipElement = null; +let hideTimer = null; + +function cancelHide() { + if (hideTimer !== null) { + clearTimeout(hideTimer); + hideTimer = null; + } +} function hideTip(name) { + cancelHide(); const el = document.getElementById(name); if (el) { try { el.hidePopover(); } catch (_) { } @@ -10,7 +19,16 @@ function hideTip(name) { currentTipElement = null; } +function scheduleHide(name) { + cancelHide(); + hideTimer = setTimeout(() => { + hideTimer = null; + hideTip(name); + }, 150); +} + function showTip(evt, name, unique) { + cancelHide(); if (currentTip === unique) return; // Hide the previously shown tooltip before showing the new one @@ -52,10 +70,16 @@ function showTip(evt, name, unique) { // Event delegation: trigger tooltips from data-fsdocs-tip attributes document.addEventListener('mouseover', function (evt) { const target = evt.target.closest('[data-fsdocs-tip]'); - if (!target) return; - const name = target.dataset.fsdocsTip; - const unique = parseInt(target.dataset.fsdocsTipUnique, 10); - showTip(evt, name, unique); + if (target) { + const name = target.dataset.fsdocsTip; + const unique = parseInt(target.dataset.fsdocsTipUnique, 10); + showTip(evt, name, unique); + return; + } + // Cancel pending hide if mouse enters the tooltip itself + if (evt.target.closest('.fsdocs-tip[popover]')) { + cancelHide(); + } }); document.addEventListener('mouseout', function (evt) { @@ -64,10 +88,11 @@ document.addEventListener('mouseout', function (evt) { // Only hide when the mouse has left the trigger element entirely if (target.contains(evt.relatedTarget)) return; const name = target.dataset.fsdocsTip; - // Don't hide if the mouse is moving into the tooltip itself (user wants to select/copy text) + // Don't hide if the mouse is moving directly into the tooltip itself const el = document.getElementById(name); if (el && el.contains(evt.relatedTarget)) return; - hideTip(name); + // Use a short delay so the mouse has time to cross any gap between trigger and tooltip + scheduleHide(name); }); // Hide the tooltip when the mouse leaves the tooltip element itself @@ -79,9 +104,7 @@ document.addEventListener('mouseout', function (evt) { // Stay open if the mouse returns to the trigger element const trigger = document.querySelector(`[data-fsdocs-tip="${tip.id}"]`); if (trigger && trigger.contains(evt.relatedTarget)) return; - try { tip.hidePopover(); } catch (_) { } - currentTip = null; - currentTipElement = null; + scheduleHide(tip.id); }); function Clipboard_CopyTo(value) { From 0271643545fe09f386fe68ee00f64e65eeb2576e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 7 Apr 2026 06:50:48 +0000 Subject: [PATCH 4/4] ci: trigger checks