Route Soma hover delays through timers

active-uix
dev 5 months ago
parent 6578cefdaf
commit 9a43cce697

@ -20,7 +20,7 @@ import {
import { linkPreviewMorfo } from '../../../morfo/components/link-preview';
import { Soma } from '../../core/soma.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
import type { TimerHandle } from '$timer';
// ── Root ─────────────────────────────────────────────────────────────────────
@ -55,8 +55,8 @@ export class LinkPreviewProvider {
readonly floatingProvider: FloatingProvider;
readonly contentPresence: Presence;
private openTimeout: ReturnType<typeof setTimeout> | null = null;
private closeTimeout: ReturnType<typeof setTimeout> | null = null;
private openTimer: TimerHandle | null = null;
private closeTimer: TimerHandle | null = null;
contentId = state('');
triggerId = state('');
@ -94,55 +94,73 @@ export class LinkPreviewProvider {
});
$effect(() => {
return () => this.clearTimeouts();
return () => this.clearTimers();
});
}
handleOpen() {
if (this.opts.disabled.current) return;
this.clearTimeouts();
this.openTimeout = setTimeout(() => {
this.openTimeout = null;
this.clearTimers();
this.openTimer = this.soma.uix.timers.schedule(
this.openTimerKey,
this.opts.openDelay.current,
() => {
this.openTimer = null;
this.opts.open.current = true;
}, this.opts.openDelay.current);
},
{ replace: true, meta: { component: 'link-preview', action: 'open-delay' } }
);
}
/** Close after closeDelay (or immediately when bypass=true, e.g. SafePolygon exit). */
handleClose(bypass = false) {
this.clearTimeouts();
this.clearTimers();
if (bypass || this.opts.closeDelay.current <= 0) {
this.opts.open.current = false;
return;
}
this.closeTimeout = setTimeout(() => {
this.closeTimeout = null;
this.closeTimer = this.soma.uix.timers.schedule(
this.closeTimerKey,
this.opts.closeDelay.current,
() => {
this.closeTimer = null;
this.opts.open.current = false;
}, this.opts.closeDelay.current);
},
{ replace: true, meta: { component: 'link-preview', action: 'close-delay' } }
);
}
/** Cancel a pending close (when pointer enters the content). */
cancelClose() {
if (this.closeTimeout !== null) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
if (this.closeTimer !== null) {
this.closeTimer.cancel();
this.closeTimer = null;
}
}
/** Immediate close bypassing delays (Escape, outside click). */
forceClose() {
this.clearTimeouts();
this.clearTimers();
this.opts.open.current = false;
}
private clearTimeouts() {
if (this.openTimeout !== null) {
clearTimeout(this.openTimeout);
this.openTimeout = null;
private clearTimers() {
if (this.openTimer !== null) {
this.openTimer.cancel();
this.openTimer = null;
}
if (this.closeTimer !== null) {
this.closeTimer.cancel();
this.closeTimer = null;
}
if (this.closeTimeout !== null) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
}
private get openTimerKey() {
return `soma:link-preview:${this.opts.id.current}:open`;
}
private get closeTimerKey() {
return `soma:link-preview:${this.opts.id.current}:close`;
}
setContentRef(el: HTMLElement | null) {

@ -31,7 +31,7 @@ import {
import { tooltipMorfo } from '../../../morfo/components/tooltip';
import { Soma } from '../../core/soma.svelte';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
import type { TimerHandle } from '$timer';
export type TooltipState = 'closed' | 'delayed-open' | 'instant-open';
@ -112,8 +112,8 @@ export class TooltipProvider {
readonly contentPresence: Presence;
private internalTooltipState = $state<TooltipState>('closed');
private openTimeout: ReturnType<typeof setTimeout> | null = null;
private closeTimeout: ReturnType<typeof setTimeout> | null = null;
private openTimer: TimerHandle | null = null;
private closeTimer: TimerHandle | null = null;
private contentRef = state<HTMLElement | null>(null);
/**
@ -167,6 +167,10 @@ export class TooltipProvider {
buffer: 2,
transitIntentTimeout: 200
});
$effect(() => {
return () => this.clearTimers();
});
}
/** Effective delay (per-tooltip override → group default). */
@ -190,28 +194,40 @@ export class TooltipProvider {
handleOpen(instant = false) {
if (this.isDisabled) return;
this.clearTimeouts();
this.clearTimers();
if (instant || this.group?.isWithinSkipDelay()) {
this.internalTooltipState = 'instant-open';
this.opts.open.current = true;
} else {
this.internalTooltipState = 'delayed-open';
this.openTimeout = setTimeout(() => {
this.openTimer = this.soma.uix.timers.schedule(
this.openTimerKey,
this.delay,
() => {
this.openTimer = null;
this.internalTooltipState = 'instant-open';
this.opts.open.current = true;
}, this.delay);
},
{ replace: true, meta: { component: 'tooltip', action: 'open-delay' } }
);
}
}
handleClose() {
this.clearTimeouts();
this.clearTimers();
const closeDelay = this.opts.closeDelay.current;
if (closeDelay > 0) {
this.closeTimeout = setTimeout(() => {
this.closeTimer = this.soma.uix.timers.schedule(
this.closeTimerKey,
closeDelay,
() => {
this.closeTimer = null;
this.doClose();
}, closeDelay);
},
{ replace: true, meta: { component: 'tooltip', action: 'close-delay' } }
);
} else {
this.doClose();
}
@ -225,21 +241,29 @@ export class TooltipProvider {
/** Cancel pending close (when pointer enters content). */
cancelClose() {
if (this.closeTimeout !== null) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
if (this.closeTimer !== null) {
this.closeTimer.cancel();
this.closeTimer = null;
}
}
private clearTimeouts() {
if (this.openTimeout !== null) {
clearTimeout(this.openTimeout);
this.openTimeout = null;
private clearTimers() {
if (this.openTimer !== null) {
this.openTimer.cancel();
this.openTimer = null;
}
if (this.closeTimer !== null) {
this.closeTimer.cancel();
this.closeTimer = null;
}
if (this.closeTimeout !== null) {
clearTimeout(this.closeTimeout);
this.closeTimeout = null;
}
private get openTimerKey() {
return `soma:tooltip:${this.opts.id.current}:open`;
}
private get closeTimerKey() {
return `soma:tooltip:${this.opts.id.current}:close`;
}
setContentRef(el: HTMLElement | null) {

Loading…
Cancel
Save

Powered by TurnKey Linux.