From ea3c4655c3023dee609865a0928ce52303a8e363 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 19:06:34 -0400 Subject: client, css: transition tooltips hidden --- src/static/client3.js | 95 ++++++++++++++++++++++++++++++++++++++++++++++----- src/static/site6.css | 8 +++++ 2 files changed, 94 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/static/client3.js b/src/static/client3.js index 390d020e..285a5ef6 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -383,6 +383,10 @@ const hoverableTooltipInfo = clientInfo.hoverableTooltipInfo = { focusInfoDelay: 750, hideTooltipDelay: 500, + + // If a tooltip that's transitioning to hidden is hovered, it'll cancel + // out of this animation immediately. + transitionHiddenDuration: 300, }, state: { @@ -399,8 +403,10 @@ const hoverableTooltipInfo = clientInfo.hoverableTooltipInfo = { focusTimeout: null, touchTimeout: null, hideTimeout: null, + transitionHiddenTimeout: null, currentlyShownTooltip: null, currentlyActiveHoverable: null, + currentlyTransitioningHiddenTooltip: null, tooltipWasJustHidden: false, hoverableWasRecentlyTouched: false, @@ -548,21 +554,36 @@ function handleTooltipReceivedFocus(tooltip) { function handleTooltipLostFocus(tooltip) { const {settings, state} = hoverableTooltipInfo; - // Hide the current tooltip right away when it loses focus. - hideCurrentlyShownTooltip(); + // Hide the current tooltip right away when it loses focus. Specify intent + // to replace - while we don't strictly know if another tooltip is going to + // immediately replace it, the mode of navigating with tab focus (once one + // tooltip has been activated) is a "switch focus immediately" kind of + // interaction in its nature. + hideCurrentlyShownTooltip(true); } function handleTooltipHoverableMouseEntered(hoverable) { const {event, settings, state} = hoverableTooltipInfo; + const {tooltip} = state.registeredHoverables.get(hoverable); + + // If this tooltip was transitioning to hidden, hovering should cancel that + // animation and show it immediately. + + if (tooltip === state.currentlyTransitioningHiddenTooltip) { + cancelTransitioningTooltipHidden(); + showTooltipFromHoverable(hoverable); + return; + } + + // Start a timer to show the corresponding tooltip, with the delay depending + // on whether fast hovering or not. This could be canceled by mousing out of + // the hoverable. const hoverTimeoutDelay = (state.fastHovering ? settings.fastHoveringInfoDelay : settings.normalHoverInfoDelay); - // Start a timer to show the corresponding tooltip, with the delay depending - // on whether fast hovering or not. This could be canceled by mousing out of - // the hoverable. state.hoverTimeout = setTimeout(() => { state.hoverTimeout = null; @@ -650,9 +671,10 @@ function handleTooltipHoverableLostFocus(hoverable, domEvent) { // Unless focus is entering the tooltip itself, hide the tooltip immediately. // This will set the tooltipWasJustHidden flag, which is detected by a newly - // focused hoverable, if applicable. + // focused hoverable, if applicable. Always specify intent to replace when + // navigating via tab focus. (Check `handleTooltipLostFocus` for details.) if (!currentlyShownTooltipHasFocus(domEvent.relatedTarget)) { - hideCurrentlyShownTooltip(); + hideCurrentlyShownTooltip(true); } } @@ -743,7 +765,48 @@ function currentlyShownTooltipHasFocus(focusElement = document.activeElement) { return false; } -function hideCurrentlyShownTooltip() { +function beginTransitioningTooltipHidden(tooltip) { + const {settings, state} = hoverableTooltipInfo; + + if (state.currentlyTransitioningHiddenTooltip) { + cancelTransitioningTooltipHidden(); + } + + tooltip.classList.add('transition-tooltip-hidden'); + tooltip.style.transitionDuration = + `${settings.transitionHiddenDuration / 1000}s`; + + state.currentlyTransitioningHiddenTooltip = tooltip; + state.transitionHiddenTimeout = + setTimeout(() => { + endTransitioningTooltipHidden(); + }, settings.transitionHiddenDuration); +} + +function cancelTransitioningTooltipHidden() { + const {state} = hoverableTooltipInfo; + + endTransitioningTooltipHidden(); + + if (state.transitionHiddenTimeout) { + clearTimeout(state.transitionHiddenTimeout); + state.transitionHiddenTimeout = null; + } +} + +function endTransitioningTooltipHidden() { + const {state} = hoverableTooltipInfo; + const {currentlyTransitioningHiddenTooltip: tooltip} = state; + + if (!tooltip) return; + + tooltip.classList.remove('transition-tooltip-hidden'); + tooltip.style.removeProperty('transition-duration'); + + state.currentlyTransitioningHiddenTooltip = null; +} + +function hideCurrentlyShownTooltip(intendingToReplace = false) { const {event, state} = hoverableTooltipInfo; const {currentlyShownTooltip: tooltip} = state; @@ -756,6 +819,14 @@ function hideCurrentlyShownTooltip() { state.currentlyActiveHoverable.classList.remove('has-visible-tooltip'); + // If there's no intent to replace this tooltip, it's the last one currently + // apparent in the interaction, and should be hidden with a transition. + if (intendingToReplace) { + cancelTransitioningTooltipHidden(); + } else { + beginTransitioningTooltipHidden(state.currentlyShownTooltip); + } + state.currentlyShownTooltip = null; state.currentlyActiveHoverable = null; @@ -774,7 +845,13 @@ function showTooltipFromHoverable(hoverable) { const {event, state} = hoverableTooltipInfo; const {tooltip} = state.registeredHoverables.get(hoverable); - if (!hideCurrentlyShownTooltip()) return false; + if (!hideCurrentlyShownTooltip(true)) return false; + + // Cancel out another tooltip that's transitioning hidden, if that's going + // on - it's a distraction that this tooltip is now replacing. + if (state.currentlyTransitioningHiddenTooltip) { + cancelTransitioningTooltipHidden(); + } hoverable.classList.add('has-visible-tooltip'); diff --git a/src/static/site6.css b/src/static/site6.css index 830e32f2..b06417db 100644 --- a/src/static/site6.css +++ b/src/static/site6.css @@ -504,6 +504,14 @@ a:not([href]):hover { display: none; } +.icons-tooltip:not(.visible).transition-tooltip-hidden { + display: block !important; + opacity: 0; + + transition-property: opacity; + transition-timing-function: linear; +} + .icons-tooltip-content { display: block; padding: 6px 2px 2px 2px; -- cgit 1.3.0-6-gf8a5