From f03ea65a10124d8962609f03d4df84be1531db17 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 18:16:09 -0400 Subject: css: adjust padding box around tooltip --- src/static/site6.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/static') diff --git a/src/static/site6.css b/src/static/site6.css index 4c083527..884cfca6 100644 --- a/src/static/site6.css +++ b/src/static/site6.css @@ -490,9 +490,9 @@ a:not([href]):hover { .icons-tooltip { position: absolute; z-index: 3; - left: -36px; - top: calc(1em - 2px); - padding: 4px 12px 6px 8px; + left: -34px; + top: calc(1em + 1px); + padding: 3px 6px 6px 6px; } .icons-tooltip:not(.visible) { -- cgit 1.3.0-6-gf8a5 From 15bc6d580ec2b3a754ff3dc17e9eb24bc90e052a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 18:16:34 -0400 Subject: client, css: style hovered/active tooltip links wavy --- src/static/client3.js | 4 ++++ src/static/site6.css | 5 +++++ 2 files changed, 9 insertions(+) (limited to 'src/static') diff --git a/src/static/client3.js b/src/static/client3.js index 866b9ba2..390d020e 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -754,6 +754,8 @@ function hideCurrentlyShownTooltip() { // Never hide the tooltip if it's focused. if (currentlyShownTooltipHasFocus()) return false; + state.currentlyActiveHoverable.classList.remove('has-visible-tooltip'); + state.currentlyShownTooltip = null; state.currentlyActiveHoverable = null; @@ -774,6 +776,8 @@ function showTooltipFromHoverable(hoverable) { if (!hideCurrentlyShownTooltip()) return false; + hoverable.classList.add('has-visible-tooltip'); + state.currentlyShownTooltip = tooltip; state.currentlyActiveHoverable = hoverable; diff --git a/src/static/site6.css b/src/static/site6.css index 884cfca6..830e32f2 100644 --- a/src/static/site6.css +++ b/src/static/site6.css @@ -482,6 +482,11 @@ a:not([href]):hover { text-decoration-style: dotted; } +.contribution.has-tooltip > a:hover, +.contribution.has-tooltip > a.has-visible-tooltip { + text-decoration-style: wavy !important; +} + .icons { font-style: normal; white-space: nowrap; -- cgit 1.3.0-6-gf8a5 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/static') 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 From db786d00e450396e680686e95db97ec353fe32f8 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 19:07:26 -0400 Subject: client, css: define tooltip transitions 100% in JS --- src/static/client3.js | 40 +++++++++++++++++++++++++++++++++------- src/static/site6.css | 8 -------- 2 files changed, 33 insertions(+), 15 deletions(-) (limited to 'src/static') diff --git a/src/static/client3.js b/src/static/client3.js index 285a5ef6..59e889a8 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -63,8 +63,25 @@ function pick(array) { return array[Math.floor(Math.random() * array.length)]; } -function cssProp(el, key) { - return getComputedStyle(el).getPropertyValue(key).trim(); +function cssProp(el, ...args) { + if (typeof args[0] === 'string' && args.length === 1) { + return getComputedStyle(el).getPropertyValue(args[0]).trim(); + } + + if (typeof args[0] === 'string' && args.length === 2) { + if (args[1] === null) { + el.style.removeProperty(args[0]); + } else { + el.style.setProperty(args[0], args[1]); + } + return; + } + + if (typeof args[0] === 'object') { + for (const [property, value] of Object.entries(args[0])) { + cssProp(el, property, value); + } + } } // TODO: These should pro8a8ly access some shared urlSpec path. We'd need to @@ -772,9 +789,13 @@ function beginTransitioningTooltipHidden(tooltip) { cancelTransitioningTooltipHidden(); } - tooltip.classList.add('transition-tooltip-hidden'); - tooltip.style.transitionDuration = - `${settings.transitionHiddenDuration / 1000}s`; + cssProp(tooltip, { + 'display': 'block', + 'opacity': '0', + 'transition-property': 'opacity', + 'transition-timing-function': 'linear', + 'transition-duration': `${settings.transitionHiddenDuration / 1000}s`, + }); state.currentlyTransitioningHiddenTooltip = tooltip; state.transitionHiddenTimeout = @@ -800,8 +821,13 @@ function endTransitioningTooltipHidden() { if (!tooltip) return; - tooltip.classList.remove('transition-tooltip-hidden'); - tooltip.style.removeProperty('transition-duration'); + cssProp(tooltip, { + 'display': null, + 'opacity': null, + 'transition-property': null, + 'transition-timing-function': null, + 'transition-duration': null, + }); state.currentlyTransitioningHiddenTooltip = null; } diff --git a/src/static/site6.css b/src/static/site6.css index b06417db..830e32f2 100644 --- a/src/static/site6.css +++ b/src/static/site6.css @@ -504,14 +504,6 @@ 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 From 0581dd1fbb7cc36cf86d28c1bb0c53264f78b213 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 19:10:14 -0400 Subject: client: transition tooltip hidden in steps --- src/static/client3.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'src/static') diff --git a/src/static/client3.js b/src/static/client3.js index 59e889a8..a0b0ed2c 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -792,9 +792,12 @@ function beginTransitioningTooltipHidden(tooltip) { cssProp(tooltip, { 'display': 'block', 'opacity': '0', + 'transition-property': 'opacity', - 'transition-timing-function': 'linear', - 'transition-duration': `${settings.transitionHiddenDuration / 1000}s`, + 'transition-timing-function': + `steps(${Math.ceil(settings.transitionHiddenDuration / 60)}, end)`, + 'transition-duration': + `${settings.transitionHiddenDuration / 1000}s`, }); state.currentlyTransitioningHiddenTooltip = tooltip; -- cgit 1.3.0-6-gf8a5 From 511a26ddff981b866e7ccb6ecb0724c0a67d097e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 19:48:53 -0400 Subject: client: handle showing/hiding tooltips without internal listeners --- src/static/client3.js | 39 +++++---------------------------------- 1 file changed, 5 insertions(+), 34 deletions(-) (limited to 'src/static') diff --git a/src/static/client3.js b/src/static/client3.js index a0b0ed2c..58687ecc 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -443,11 +443,6 @@ const hoverableTooltipInfo = clientInfo.hoverableTooltipInfo = { currentTouchIdentifiers: new Set(), touchIdentifiersBanishedByScrolling: new Set(), }, - - event: { - whenTooltipShouldBeShown: [], - whenTooltipShouldBeHidden: [], - }, }; // Adds DOM event listeners, so must be called during addPageListeners step. @@ -856,6 +851,9 @@ function hideCurrentlyShownTooltip(intendingToReplace = false) { beginTransitioningTooltipHidden(state.currentlyShownTooltip); } + tooltip.classList.remove('visible'); + tooltip.inert = true; + state.currentlyShownTooltip = null; state.currentlyActiveHoverable = null; @@ -865,8 +863,6 @@ function hideCurrentlyShownTooltip(intendingToReplace = false) { state.tooltipWasJustHidden = false; }); - dispatchInternalEvent(event, 'whenTooltipShouldBeHidden', {tooltip}); - return true; } @@ -883,14 +879,14 @@ function showTooltipFromHoverable(hoverable) { } hoverable.classList.add('has-visible-tooltip'); + tooltip.classList.add('visible'); + tooltip.inert = false; state.currentlyShownTooltip = tooltip; state.currentlyActiveHoverable = hoverable; state.tooltipWasJustHidden = false; - dispatchInternalEvent(event, 'whenTooltipShouldBeShown', {hoverable, tooltip}); - return true; } @@ -1979,30 +1975,6 @@ function getExternalIconTooltipReferences() { .map(span => span.querySelector('span.icons-tooltip')); } -function addExternalIconTooltipInternalListeners() { - const info = externalIconTooltipInfo; - - hoverableTooltipInfo.event.whenTooltipShouldBeShown.push(({tooltip}) => { - if (!info.iconContainers.includes(tooltip)) return; - showExternalIconTooltip(tooltip); - }); - - hoverableTooltipInfo.event.whenTooltipShouldBeHidden.push(({tooltip}) => { - if (!info.iconContainers.includes(tooltip)) return; - hideExternalIconTooltip(tooltip); - }); -} - -function showExternalIconTooltip(iconContainer) { - iconContainer.classList.add('visible'); - iconContainer.inert = false; -} - -function hideExternalIconTooltip(iconContainer) { - iconContainer.classList.remove('visible'); - iconContainer.inert = true; -} - function addExternalIconTooltipPageListeners() { const info = externalIconTooltipInfo; @@ -2016,7 +1988,6 @@ function addExternalIconTooltipPageListeners() { } clientSteps.getPageReferences.push(getExternalIconTooltipReferences); -clientSteps.addInternalListeners.push(addExternalIconTooltipInternalListeners); clientSteps.addPageListeners.push(addExternalIconTooltipPageListeners); /* -- cgit 1.3.0-6-gf8a5 From 57c06ea665cf2c2ee4536cab70b2459457d05e15 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 19:53:45 -0400 Subject: static, css: define tooltip show/hide 100% in JS --- src/static/client3.js | 5 +++-- src/static/site6.css | 3 --- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'src/static') diff --git a/src/static/client3.js b/src/static/client3.js index 58687ecc..a55361ce 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -847,11 +847,11 @@ function hideCurrentlyShownTooltip(intendingToReplace = false) { // apparent in the interaction, and should be hidden with a transition. if (intendingToReplace) { cancelTransitioningTooltipHidden(); + cssProp(tooltip, 'display', 'none'); } else { beginTransitioningTooltipHidden(state.currentlyShownTooltip); } - tooltip.classList.remove('visible'); tooltip.inert = true; state.currentlyShownTooltip = null; @@ -879,7 +879,8 @@ function showTooltipFromHoverable(hoverable) { } hoverable.classList.add('has-visible-tooltip'); - tooltip.classList.add('visible'); + + cssProp(tooltip, 'display', 'block'); tooltip.inert = false; state.currentlyShownTooltip = tooltip; diff --git a/src/static/site6.css b/src/static/site6.css index 830e32f2..76b58f32 100644 --- a/src/static/site6.css +++ b/src/static/site6.css @@ -498,9 +498,6 @@ a:not([href]):hover { left: -34px; top: calc(1em + 1px); padding: 3px 6px 6px 6px; -} - -.icons-tooltip:not(.visible) { display: none; } -- cgit 1.3.0-6-gf8a5 From 930bb9e0f1fc7167dbf53636246e3cd2de773774 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Nov 2023 19:55:04 -0400 Subject: client: grace period during transition hidden --- src/static/client3.js | 52 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 14 deletions(-) (limited to 'src/static') diff --git a/src/static/client3.js b/src/static/client3.js index a55361ce..86b5f985 100644 --- a/src/static/client3.js +++ b/src/static/client3.js @@ -401,9 +401,11 @@ const hoverableTooltipInfo = clientInfo.hoverableTooltipInfo = { hideTooltipDelay: 500, - // If a tooltip that's transitioning to hidden is hovered, it'll cancel - // out of this animation immediately. + // If a tooltip that's transitioning to hidden is hovered during the grace + // period (or the corresponding hoverable is hovered at any point in the + // transition), it'll cancel out of this animation immediately. transitionHiddenDuration: 300, + inertGracePeriod: 100, }, state: { @@ -421,9 +423,11 @@ const hoverableTooltipInfo = clientInfo.hoverableTooltipInfo = { touchTimeout: null, hideTimeout: null, transitionHiddenTimeout: null, + inertGracePeriodTimeout: null, currentlyShownTooltip: null, currentlyActiveHoverable: null, currentlyTransitioningHiddenTooltip: null, + previouslyActiveHoverable: null, tooltipWasJustHidden: false, hoverableWasRecentlyTouched: false, @@ -526,9 +530,15 @@ function registerTooltipHoverableElement(hoverable, tooltip) { function handleTooltipMouseEntered(tooltip) { const {state} = hoverableTooltipInfo; + if (state.currentlyTransitioningHiddenTooltip) { + cancelTransitioningTooltipHidden(true); + return; + } + if (state.currentlyShownTooltip !== tooltip) return; // Don't time out the current tooltip while hovering it. + if (state.hideTimeout) { clearTimeout(state.hideTimeout); state.hideTimeout = null; @@ -582,8 +592,7 @@ function handleTooltipHoverableMouseEntered(hoverable) { // animation and show it immediately. if (tooltip === state.currentlyTransitioningHiddenTooltip) { - cancelTransitioningTooltipHidden(); - showTooltipFromHoverable(hoverable); + cancelTransitioningTooltipHidden(true); return; } @@ -802,14 +811,13 @@ function beginTransitioningTooltipHidden(tooltip) { }, settings.transitionHiddenDuration); } -function cancelTransitioningTooltipHidden() { +function cancelTransitioningTooltipHidden(andShow = false) { const {state} = hoverableTooltipInfo; endTransitioningTooltipHidden(); - if (state.transitionHiddenTimeout) { - clearTimeout(state.transitionHiddenTimeout); - state.transitionHiddenTimeout = null; + if (andShow) { + showTooltipFromHoverable(state.previouslyActiveHoverable); } } @@ -828,10 +836,20 @@ function endTransitioningTooltipHidden() { }); state.currentlyTransitioningHiddenTooltip = null; + + if (state.inertGracePeriodTimeout) { + clearTimeout(state.inertGracePeriodTimeout); + state.inertGracePeriodTimeout = null; + } + + if (state.transitionHiddenTimeout) { + clearTimeout(state.transitionHiddenTimeout); + state.transitionHiddenTimeout = null; + } } function hideCurrentlyShownTooltip(intendingToReplace = false) { - const {event, state} = hoverableTooltipInfo; + const {event, settings, state} = hoverableTooltipInfo; const {currentlyShownTooltip: tooltip} = state; // If there was no tooltip to begin with, we're functionally in the desired @@ -846,13 +864,21 @@ function hideCurrentlyShownTooltip(intendingToReplace = false) { // 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(); cssProp(tooltip, 'display', 'none'); } else { beginTransitioningTooltipHidden(state.currentlyShownTooltip); } - tooltip.inert = true; + // Wait just a moment before making the tooltip inert. You might react + // (to the ghosting, or just to time passing) and realize you wanted + // to look at the tooltip after all - this delay gives a little buffer + // to second guess letting it disappear. + state.inertGracePeriodTimeout = + setTimeout(() => { + tooltip.inert = true; + }, settings.inertGracePeriod); + + state.previouslyActiveHoverable = state.currentlyActiveHoverable; state.currentlyShownTooltip = null; state.currentlyActiveHoverable = null; @@ -874,9 +900,7 @@ function showTooltipFromHoverable(hoverable) { // 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(); - } + cancelTransitioningTooltipHidden(); hoverable.classList.add('has-visible-tooltip'); -- cgit 1.3.0-6-gf8a5