import { isLocationStateEntry, resolveLocationStateEntries, } from "./react/location-state-shared.js"; /** * Check if state is from typed LocationStateEntry[] (has __rsc_ls_ keys) */ function isTypedLocationState( state: unknown, ): state is Record { if (state === null || typeof state !== "object") return false; return Object.keys(state).some((key) => key.startsWith("__rsc_ls_")); } /** * Resolve navigation state - handles both LocationStateEntry[] and plain formats */ export function resolveNavigationState(state: unknown): unknown { if ( Array.isArray(state) && state.length > 0 && isLocationStateEntry(state[0]) ) { return resolveLocationStateEntries(state); } return state; } /** * Build history state object from user state * - Typed state: spread directly into history.state * - Plain state: store in history.state.state */ export function buildHistoryState( userState: unknown, routerState?: { intercept?: boolean; sourceUrl?: string }, serverState?: Record, ): Record | null { const result: Record = {}; if (routerState?.intercept) { result.intercept = true; if (routerState.sourceUrl) { result.sourceUrl = routerState.sourceUrl; } } if (userState !== undefined) { if (isTypedLocationState(userState)) { Object.assign(result, userState); } else { result.state = userState; } } if (serverState) { Object.assign(result, serverState); } return Object.keys(result).length > 0 ? result : null; } /** * Stamp an `idx` on the next history entry's state and call push/replaceState. * Push increments the current idx; replace keeps it. Initial entry idx is 0. * Used by useRouter().back() to detect "first entry in this session" without * relying on the Navigation API. */ export function pushHistoryWithIdx( state: Record | null, url: string, replace: boolean, ): void { const oldIdx = (window.history.state as { idx?: number } | null)?.idx ?? 0; const newIdx = replace ? oldIdx : oldIdx + 1; const finalState = { ...(state ?? {}), idx: newIdx }; if (replace) { window.history.replaceState(finalState, "", url); } else { window.history.pushState(finalState, "", url); } } /** * Merge server-set location state into the current history entry. * Replaces the current history state and dispatches notification event * so useLocationState hooks re-read from history.state. */ export function mergeLocationState( locationState: Record, ): void { const merged = { ...window.history.state, ...locationState, }; window.history.replaceState(merged, "", window.location.href); if (Object.keys(locationState).some((k) => k.startsWith("__rsc_ls_"))) { window.dispatchEvent(new Event("__rsc_locationstate")); } }