feat: add tooltip transition status

This commit is contained in:
Wesley Luyten
2025-10-13 16:26:10 -07:00
parent 871701a453
commit 05988a82ba
2 changed files with 40 additions and 11 deletions
@@ -11,6 +11,7 @@ export class MediaTooltipRoot extends HTMLElement {
#cleanup: (() => void) | null = null;
#arrowElement: HTMLElement | null = null;
#mousePosition = { x: 0, y: 0 };
#transitionStatus: 'initial' | 'open' | 'close' | 'unmounted' = 'initial';
constructor() {
super();
@@ -36,6 +37,9 @@ export class MediaTooltipRoot extends HTMLElement {
disconnectedCallback(): void {
this.#clearHoverTimeout();
this.#cleanup?.();
this.#transitionStatus = 'unmounted';
this.#updateVisibility();
}
static get observedAttributes(): string[] {
@@ -75,6 +79,17 @@ export class MediaTooltipRoot extends HTMLElement {
if (this.#open === open) return;
this.#open = open;
if (open) {
this.#transitionStatus = 'initial';
requestAnimationFrame(() => {
this.#transitionStatus = 'open';
this.#updateVisibility();
});
} else {
this.#transitionStatus = 'close';
}
this.#updateVisibility();
if (open) {
@@ -86,17 +101,22 @@ export class MediaTooltipRoot extends HTMLElement {
}
#updateVisibility(): void {
this.toggleAttribute('data-open', this.#open);
this.style.display = 'contents';
if (this.#popupElement) {
this.#popupElement.style.display = this.#open ? 'block' : 'none';
const placement = this.#positionerElement?.side ?? 'top';
this.#popupElement.setAttribute('data-side', placement);
this.#popupElement.toggleAttribute('data-starting-style', this.#transitionStatus === 'initial');
this.#popupElement.toggleAttribute('data-open', this.#transitionStatus === 'initial' || this.#transitionStatus === 'open');
this.#popupElement.toggleAttribute('data-ending-style', this.#transitionStatus === 'close' || this.#transitionStatus === 'unmounted');
this.#popupElement.toggleAttribute('data-closed', this.#transitionStatus === 'close' || this.#transitionStatus === 'unmounted');
}
// Update trigger aria-expanded
const triggerElement = this.#triggerElement?.firstElementChild as HTMLElement;
if (triggerElement) {
triggerElement.setAttribute('aria-expanded', this.#open.toString());
triggerElement.toggleAttribute('data-popup-open', this.#open);
}
}
@@ -125,19 +145,16 @@ export class MediaTooltipRoot extends HTMLElement {
}),
];
// Add arrow middleware if arrow element exists
if (this.#arrowElement) {
middleware.push(arrow({ element: this.#arrowElement }));
}
// Use cursor position for positioning if trackCursorAxis is enabled
const referenceElement = this.trackCursorAxis
? {
getBoundingClientRect: () => {
const triggerRect = trigger.getBoundingClientRect();
if (this.trackCursorAxis === 'x') {
// Track horizontal cursor position, use trigger's vertical position
return {
width: 0,
height: 0,
@@ -149,7 +166,6 @@ export class MediaTooltipRoot extends HTMLElement {
y: triggerRect.top,
};
} else if (this.trackCursorAxis === 'y') {
// Track vertical cursor position, use trigger's horizontal position
return {
width: 0,
height: 0,
@@ -180,13 +196,14 @@ export class MediaTooltipRoot extends HTMLElement {
computePosition(referenceElement, popup, {
placement,
middleware,
}).then(({ x, y, middlewareData }: { x: number; y: number; middlewareData: any }) => {
}).then(({ x, y, middlewareData, placement: computedPlacement }: { x: number; y: number; middlewareData: any; placement: Placement }) => {
Object.assign(popup.style, {
left: `${x}px`,
top: `${y}px`,
});
// Position arrow if it exists
popup.setAttribute('data-side', computedPlacement);
if (this.#arrowElement && middlewareData.arrow) {
const { x: arrowX, y: arrowY } = middlewareData.arrow;
Object.assign(this.#arrowElement.style, {
@@ -234,7 +251,7 @@ export class MediaTooltipRoot extends HTMLElement {
#handleMouseMove(event: MouseEvent): void {
if (this.trackCursorAxis) {
this.#mousePosition = { x: event.clientX, y: event.clientY };
// Update position if tooltip is open
if (this.#open) {
this.#updatePosition();
}
@@ -371,7 +388,6 @@ export class MediaTooltipPositioner extends HTMLElement {
export class MediaTooltipPopup extends HTMLElement {
connectedCallback(): void {
this.setAttribute('role', 'tooltip');
this.style.display = 'none';
}
}
@@ -253,6 +253,19 @@ export function getTemplateHTML() {
font-size: 13px;
font-family: system-ui, -apple-system, sans-serif;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--transition: .15s ease-in-out;
transition: transform var(--transition), scale var(--transition), opacity var(--transition);
}
media-tooltip-popup[data-starting-style] {
transition-duration: 0s;
transform: scale(0.9);
opacity: 0;
}
media-tooltip-popup[data-ending-style] {
transform: scale(0.9);
opacity: 0;
}
.tooltip {