From 3d414599361015b308a645ec0b3a1c53caf4ae7d Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 21 Jun 2026 00:57:00 +0200 Subject: [PATCH] =?UTF-8?q?fix(tooltip):=20the=20hover=20entrance=20animat?= =?UTF-8?q?ion=20never=20played=20=E2=80=94=20content=20mounted=20as=20ins?= =?UTF-8?q?tant-open?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tooltip's `delayed-open` entrance (the per-side slide, and now the bounce) NEVER fired: the content only mounts when `open` becomes true, and the hover-delay timer set `internalTooltipState = 'instant-open'` AT THAT MOMENT — so the content always mounted as `instant-open` (no entrance). `delayed-open` was only ever the pending state while the content was still unmounted. A long-standing dead animation. Fix: the hover-delay timer now keeps `'delayed-open'` as the content mounts, so the eidos `[data-state='delayed-open']` entrance plays. `'instant-open'` stays reserved for focus / skip-delay (intentionally no entrance). Test updated (it asserted the buggy `instant-open`); tooltip provider suite 4/4. Also makes the bounce clearly perceptible (it was too subtle to notice even when forced): scale 0.5 → 1.18 (overshoot) → 0.9 (undershoot) → 1.06 (second bounce) → 1, over `--duration-slow`. Verified end-to-end in-browser: hover → content mounts `delayed-open` → `animation-name: eidos-tooltip-bounce-in`. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/uix/eidos/components/tooltip/tooltip.css | 15 ++++++++------- .../tooltip/tooltip-provider.svelte.test.ts | 5 +++-- .../components/tooltip/tooltip-provider.svelte.ts | 7 ++++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/uix/eidos/components/tooltip/tooltip.css b/src/uix/eidos/components/tooltip/tooltip.css index e8affad41..75d5f0f4f 100644 --- a/src/uix/eidos/components/tooltip/tooltip.css +++ b/src/uix/eidos/components/tooltip/tooltip.css @@ -96,7 +96,7 @@ * registered `motion` preset. */ [data-tooltip-content][data-state='delayed-open'] { - animation: eidos-tooltip-bounce-in var(--duration-moderate) var(--ease-out) both; + animation: eidos-tooltip-bounce-in var(--duration-slow) var(--ease-out) both; } [data-tooltip-content][data-state='closed'] { animation: eidos-tooltip-bounce-out var(--duration-fast) var(--ease-in) both; @@ -108,14 +108,15 @@ transform: none; } -/* A real bounce: grow past 1 (overshoot 1.06), dip slightly under (undershoot - * 0.97), settle to 1. Subtle in absolute terms (a tooltip is small) but clearly a - * "rebote". `scale` (the individual property, not the `transform` shorthand) +/* A real bounce (muelle): grow from half size, overshoot clearly past 1, dip + * under, a smaller second overshoot, then settle — a perceptible spring, not a + * subtle pop. `scale` (the individual property, not the `transform` shorthand) * composes with `transform-origin` and never clobbers a sibling transform. */ @keyframes eidos-tooltip-bounce-in { - 0% { opacity: 0; scale: 0.7; } - 50% { opacity: 1; scale: 1.06; } - 74% { scale: 0.97; } + 0% { opacity: 0; scale: 0.5; } + 38% { opacity: 1; scale: 1.18; } + 60% { scale: 0.9; } + 80% { scale: 1.06; } 100% { scale: 1; } } @keyframes eidos-tooltip-bounce-out { diff --git a/src/uix/soma/components/tooltip/tooltip-provider.svelte.test.ts b/src/uix/soma/components/tooltip/tooltip-provider.svelte.test.ts index 6c8fb291a..14aa40ec7 100644 --- a/src/uix/soma/components/tooltip/tooltip-provider.svelte.test.ts +++ b/src/uix/soma/components/tooltip/tooltip-provider.svelte.test.ts @@ -151,8 +151,9 @@ describe('TooltipProvider', () => { harness.tasks.get('soma:tooltip:tooltip-root:open')?.(); await tick(); expect(opts.open.current).toBe(true); - expect(result.provider.tooltipState).toBe('instant-open'); - expect(triggerEl.getAttribute('data-state')).toBe('instant-open'); + // Hover-delay path opens with the ENTRANCE state so the eidos animation plays. + expect(result.provider.tooltipState).toBe('delayed-open'); + expect(triggerEl.getAttribute('data-state')).toBe('delayed-open'); result.trigger.onpointerleave(); expect(harness.schedule).toHaveBeenCalledWith( diff --git a/src/uix/soma/components/tooltip/tooltip-provider.svelte.ts b/src/uix/soma/components/tooltip/tooltip-provider.svelte.ts index 0e29db26d..19c9b9a18 100644 --- a/src/uix/soma/components/tooltip/tooltip-provider.svelte.ts +++ b/src/uix/soma/components/tooltip/tooltip-provider.svelte.ts @@ -220,7 +220,12 @@ export class TooltipProvider { this.delay, () => { this.openTimer = null; - this.internalTooltipState = 'instant-open'; + // Stay 'delayed-open' (NOT 'instant-open') as the content MOUNTS, so the + // eidos entrance animation keyed on `[data-state='delayed-open']` actually + // plays. The content only mounts here (open → true), so setting + // 'instant-open' meant the hover entrance NEVER fired. 'instant-open' stays + // reserved for focus / skip-delay (no entrance). + this.internalTooltipState = 'delayed-open'; this.opts.open.current = true; }, { replace: true, meta: { component: 'tooltip', action: 'open-delay' } }