63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
export const SIDEBAR_COLLAPSE_DURATION_MS = 300;
|
|
export const SIDEBAR_TRANSITION_FALLBACK_MS = SIDEBAR_COLLAPSE_DURATION_MS + 80;
|
|
// Hover expansion and hidden-tab rAF suspension must never hold the detail
|
|
// shell indefinitely. Settling is preferred, but this is the absolute cap.
|
|
export const SIDEBAR_DETAILS_REVEAL_DEADLINE_MS = SIDEBAR_TRANSITION_FALLBACK_MS * 2;
|
|
|
|
export function sidebarDetailsRevealDelay(reduceMotion: boolean): number {
|
|
return reduceMotion ? 0 : SIDEBAR_TRANSITION_FALLBACK_MS;
|
|
}
|
|
|
|
export function isCollapsedSidebarSurface(target: EventTarget | null): target is HTMLElement {
|
|
return (
|
|
target instanceof HTMLElement &&
|
|
target.classList.contains("sidebar-surface") &&
|
|
target.dataset.collapsed === "true"
|
|
);
|
|
}
|
|
|
|
export function hasRunningSidebarTransition(surface: HTMLElement): boolean {
|
|
if (typeof surface.getAnimations !== "function") return false;
|
|
const Transition = globalThis.CSSTransition;
|
|
if (typeof Transition === "undefined") return false;
|
|
return surface
|
|
.getAnimations()
|
|
.some(
|
|
(animation) =>
|
|
animation instanceof Transition &&
|
|
animation.transitionProperty === "transform" &&
|
|
animation.playState === "running",
|
|
);
|
|
}
|
|
|
|
export function parseOptionalLibraryId(rawLibraryId: string | null): number | undefined {
|
|
if (!rawLibraryId) return undefined;
|
|
const parsedLibraryId = Number(rawLibraryId);
|
|
return Number.isFinite(parsedLibraryId) ? parsedLibraryId : undefined;
|
|
}
|
|
|
|
export interface SidebarItemNavigationRequest {
|
|
href: string;
|
|
replace?: boolean;
|
|
state?: unknown;
|
|
}
|
|
|
|
export function parseItemNavigationHref(
|
|
href: string,
|
|
origin: string,
|
|
): { contentId: string; libraryId?: number } | null {
|
|
try {
|
|
const destination = new URL(href, origin);
|
|
if (destination.origin !== origin || !destination.pathname.startsWith("/item/")) return null;
|
|
|
|
const contentId = decodeURIComponent(destination.pathname.slice("/item/".length));
|
|
if (!contentId) return null;
|
|
return {
|
|
contentId,
|
|
libraryId: parseOptionalLibraryId(destination.searchParams.get("libraryId")),
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|