|
|
|
@ -1,12 +1,11 @@
|
|
|
|
import {
|
|
|
|
import { type Active, type ActiveProps } from '$libs/reactive';
|
|
|
|
type Active,
|
|
|
|
|
|
|
|
type ActiveProps
|
|
|
|
|
|
|
|
} from '$libs/reactive';
|
|
|
|
|
|
|
|
import { watch } from 'runed';
|
|
|
|
import { watch } from 'runed';
|
|
|
|
|
|
|
|
import type { ActiveDom } from '$adom';
|
|
|
|
|
|
|
|
|
|
|
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
// ── Types ────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export interface PresenceOptions extends ActiveProps<{ open: boolean; ref: HTMLElement | null }> {
|
|
|
|
export interface PresenceOptions extends ActiveProps<{ open: boolean; ref: HTMLElement | null }> {
|
|
|
|
|
|
|
|
dom: ActiveDom;
|
|
|
|
/** Called after open/close animation completes. Receives the current open state. */
|
|
|
|
/** Called after open/close animation completes. Receives the current open state. */
|
|
|
|
onComplete?: (open: boolean) => void;
|
|
|
|
onComplete?: (open: boolean) => void;
|
|
|
|
/** When false, skips animation waiting — unmounts immediately on close. @default true */
|
|
|
|
/** When false, skips animation waiting — unmounts immediately on close. @default true */
|
|
|
|
@ -39,6 +38,7 @@ export class Presence {
|
|
|
|
transitionStatus = $state<TransitionStatus>(undefined);
|
|
|
|
transitionStatus = $state<TransitionStatus>(undefined);
|
|
|
|
|
|
|
|
|
|
|
|
private runId = 0;
|
|
|
|
private runId = 0;
|
|
|
|
|
|
|
|
private frameIds = new Set<number>();
|
|
|
|
|
|
|
|
|
|
|
|
constructor(opts: PresenceOptions) {
|
|
|
|
constructor(opts: PresenceOptions) {
|
|
|
|
this.opts = opts;
|
|
|
|
this.opts = opts;
|
|
|
|
@ -76,7 +76,7 @@ export class Presence {
|
|
|
|
const runId = ++this.runId;
|
|
|
|
const runId = ++this.runId;
|
|
|
|
|
|
|
|
|
|
|
|
// Remove data-starting-style on next frame to trigger CSS transition
|
|
|
|
// Remove data-starting-style on next frame to trigger CSS transition
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
this.requestFrame(() => {
|
|
|
|
if (runId !== this.runId) return;
|
|
|
|
if (runId !== this.runId) return;
|
|
|
|
this.transitionStatus = undefined;
|
|
|
|
this.transitionStatus = undefined;
|
|
|
|
|
|
|
|
|
|
|
|
@ -117,7 +117,7 @@ export class Presence {
|
|
|
|
|
|
|
|
|
|
|
|
private waitForAnimations(runId: number, onDone: () => void) {
|
|
|
|
private waitForAnimations(runId: number, onDone: () => void) {
|
|
|
|
// getAnimations() needs a frame to see the active animations
|
|
|
|
// getAnimations() needs a frame to see the active animations
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
this.requestFrame(() => {
|
|
|
|
if (runId !== this.runId) return;
|
|
|
|
if (runId !== this.runId) return;
|
|
|
|
|
|
|
|
|
|
|
|
const node = this.opts.ref.current;
|
|
|
|
const node = this.opts.ref.current;
|
|
|
|
@ -145,7 +145,19 @@ export class Presence {
|
|
|
|
|
|
|
|
|
|
|
|
// ── Cleanup ──────────────────────────────────────────────────────────────
|
|
|
|
// ── Cleanup ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private requestFrame(callback: () => void) {
|
|
|
|
|
|
|
|
const frame = this.opts.dom.requestFrame(() => {
|
|
|
|
|
|
|
|
this.frameIds.delete(frame);
|
|
|
|
|
|
|
|
callback();
|
|
|
|
|
|
|
|
}, this.opts.ref.current);
|
|
|
|
|
|
|
|
this.frameIds.add(frame);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private cleanup() {
|
|
|
|
private cleanup() {
|
|
|
|
this.runId++;
|
|
|
|
this.runId++;
|
|
|
|
|
|
|
|
for (const frame of this.frameIds) {
|
|
|
|
|
|
|
|
this.opts.dom.cancelFrame(frame, this.opts.ref.current);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
this.frameIds.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|