« get me outta code hell

client, css: transition tooltips hidden - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-11-26 19:06:34 -0400
committer(quasar) nebula <qznebula@protonmail.com>2023-11-26 19:57:02 -0400
commitea3c4655c3023dee609865a0928ce52303a8e363 (patch)
treec52665ae09c21f07b8c3c12f18b67ff5595d3fb4 /src
parent15bc6d580ec2b3a754ff3dc17e9eb24bc90e052a (diff)
client, css: transition tooltips hidden
Diffstat (limited to 'src')
-rw-r--r--src/static/client3.js95
-rw-r--r--src/static/site6.css8
2 files changed, 94 insertions, 9 deletions
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;