Sign In

@tanstack/react-start-client

Package Overview
Dependencies
Maintainers
5
Versions
494
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tanstack/react-start-client - npm Package Compare versions

Comparing version
1.167.4
to
1.168.0
+3
dist/esm/GenericHydrate.d.ts
import { HydrateProps } from './Hydrate.js';
import * as React from 'react';
export declare function GenericHydrate(props: HydrateProps): React.JSX.Element;
"use client";
import { reactUse, useHydrated, useLayoutEffect } from "@tanstack/react-router";
import { jsx } from "react/jsx-runtime";
import * as React from "react";
import { isServer } from "@tanstack/router-core/isServer";
import { listenForDelegatedHydrationIntent } from "@tanstack/start-client-core/hydration";
import { hydrateIdAttribute, hydrateWhenAttribute } from "@tanstack/start-client-core/hydration/constants";
import { createResolvedGate, getFallbackHtml, getOrCreateGate, onGateResolve, releaseGate, runHydrationStrategyCleanup, saveFallbackHtml, waitForHydrationPrefetchStrategy } from "@tanstack/start-client-core/hydration/runtime";
//#region src/GenericHydrate.tsx
var dynamicType = "dynamic";
var dynamicHydrateStrategy = {
_t: dynamicType,
_d: () => true
};
function shouldDeferHydration(strategy) {
return strategy._d ? strategy._d() : strategy._t !== "load";
}
function useLatest(value) {
const ref = React.useRef(value);
ref.current = value;
return ref;
}
function useHydrationGate(props) {
const hydrated = useHydrated();
const reactId = React.useId();
const id = props.h ? `${props.h}${reactId}` : reactId;
const when = props.when;
const isDynamicHydrate = typeof when === "function";
const dynamicHydrateStrategyRef = React.useRef(void 0);
if (isDynamicHydrate) dynamicHydrateStrategyRef.current ??= isServer ?? typeof window === "undefined" ? dynamicHydrateStrategy : when();
const hydrateStrategy = isDynamicHydrate ? dynamicHydrateStrategyRef.current : when;
const markerHydrateType = isDynamicHydrate ? dynamicType : hydrateStrategy._t;
const [prefetchError, setPrefetchError] = React.useState();
const latestRef = useLatest({
prefetch: props.prefetch,
preload: props.p
});
const gateRef = React.useRef(void 0);
const markerElementRef = React.useRef(null);
const shouldPreserveServerHTMLRef = React.useRef(void 0);
const shouldDeferInitialHydrationRef = React.useRef(void 0);
const didPrefetchRef = React.useRef(false);
const prefetchControllerRef = React.useRef(void 0);
prefetchControllerRef.current ??= {
abortController: new AbortController(),
hydrationRequested: false,
hydrationListeners: /* @__PURE__ */ new Set(),
hydrationResolvePending: false,
started: false
};
shouldPreserveServerHTMLRef.current ??= (isServer ?? typeof window === "undefined") || !hydrated;
shouldDeferInitialHydrationRef.current ??= !hydrated && shouldDeferHydration(hydrateStrategy);
if (!gateRef.current) gateRef.current = isServer ?? typeof window === "undefined" ? createResolvedGate(id, hydrateStrategy._t) : getOrCreateGate(id, hydrateStrategy._t);
gateRef.current.when = hydrateStrategy._t;
if (!(isServer ?? typeof window === "undefined") && hydrateStrategy._t !== "never" && (!shouldDeferInitialHydrationRef.current || !shouldDeferHydration(hydrateStrategy))) gateRef.current.resolve();
const markerRef = React.useCallback((element) => {
markerElementRef.current = element;
if (element) {
if (hydrateStrategy._t === "never" && !shouldPreserveServerHTMLRef.current) element.replaceChildren();
saveFallbackHtml(id, element);
}
}, [hydrateStrategy._t, id]);
React.useEffect(() => {
const gate = gateRef.current;
return () => {
const controller = prefetchControllerRef.current;
controller?.abortController.abort();
controller?.cleanup?.();
controller?.hydrationListeners.clear();
releaseGate(gate);
};
}, []);
React.useEffect(() => {
if ((isServer ?? typeof window === "undefined") || !latestRef.current.prefetch) return;
const controller = prefetchControllerRef.current;
if (controller.started) return;
controller.started = true;
const onHydrate = (listener) => {
if (controller.hydrationRequested) {
listener();
return () => {};
}
controller.hydrationListeners.add(listener);
return () => {
controller.hydrationListeners.delete(listener);
};
};
const preload = () => latestRef.current.preload?.() ?? Promise.resolve();
const prefetchInput = latestRef.current.prefetch;
if (typeof prefetchInput === "function") {
const promise = Promise.resolve().then(() => prefetchInput({
element: markerElementRef.current,
signal: controller.abortController.signal,
preload,
waitFor: (strategy) => waitForHydrationPrefetchStrategy(strategy, {
element: markerElementRef.current,
signal: controller.abortController.signal,
onHydrate
})
})).then(() => void 0);
controller.promise = promise;
promise.catch((error) => {
if (!controller.abortController.signal.aborted) setPrefetchError(error);
});
return;
}
if (!latestRef.current.preload) return;
const prefetch = () => {
if (didPrefetchRef.current) return;
didPrefetchRef.current = true;
preload();
};
controller.cleanup = runHydrationStrategyCleanup(prefetchInput._s?.({
element: markerElementRef.current,
prefetch
}));
}, [hydrateStrategy, latestRef]);
useLayoutEffect(() => {
const gate = gateRef.current;
if (!shouldDeferInitialHydrationRef.current || hydrateStrategy._t === "never") return;
if (gate.resolved) return;
const cleanups = [];
let removeResolveListener = () => {};
let disposed = false;
const resolveGate = gate.resolve;
const cleanup = () => {
if (disposed) return;
disposed = true;
if (gate.resolve === requestHydration) gate.resolve = resolveGate;
removeResolveListener();
cleanups.forEach((fn) => fn());
};
const addCleanup = (fn) => {
if (!fn) return;
if (disposed || gate.resolved) {
fn();
return;
}
cleanups.push(fn);
};
const requestHydration = () => {
const controller = prefetchControllerRef.current;
if (!controller.hydrationRequested) {
controller.hydrationRequested = true;
controller.hydrationListeners.forEach((listener) => listener());
controller.hydrationListeners.clear();
}
if (!controller.promise) {
resolveGate();
return;
}
if (controller.hydrationResolvePending) return;
controller.hydrationResolvePending = true;
controller.promise.then(() => resolveGate(), (error) => {
if (!controller.abortController.signal.aborted) setPrefetchError(error);
});
};
gate.resolve = requestHydration;
removeResolveListener = onGateResolve(gate, cleanup);
const context = {
element: markerElementRef.current,
gate
};
addCleanup(runHydrationStrategyCleanup(hydrateStrategy._s?.(context)));
if (hydrateStrategy._t !== "interaction") addCleanup(runHydrationStrategyCleanup(markerElementRef.current ? listenForDelegatedHydrationIntent(markerElementRef.current, context) : void 0));
return cleanup;
}, [hydrateStrategy, latestRef]);
return {
gate: gateRef.current,
markerRef,
markerElementRef,
hydrateStrategy,
markerHydrateType,
prefetchError,
shouldPreserveServerHTML: shouldPreserveServerHTMLRef.current
};
}
function HydrationGate(props) {
if (isServer ?? typeof window === "undefined") return props.children;
if (props.gate.resolved) return props.children;
if (!reactUse) throw props.gate.promise;
reactUse(props.gate.promise);
return props.children;
}
function HydratedBoundary(props) {
const { id, onHydrated, onStrategyHydrated } = props;
const didHydrateRef = React.useRef(false);
React.useEffect(() => {
if (didHydrateRef.current) return;
didHydrateRef.current = true;
onHydrated?.();
onStrategyHydrated?.(id);
}, [
id,
onHydrated,
onStrategyHydrated
]);
return props.children;
}
function GenericHydrate(props) {
const { gate, hydrateStrategy, markerHydrateType, markerElementRef, markerRef, prefetchError, shouldPreserveServerHTML } = useHydrationGate(props);
if (prefetchError) throw prefetchError;
const fallback = shouldPreserveServerHTML ? (() => {
const html = getFallbackHtml(gate.id);
return html ? /* @__PURE__ */ jsx("div", {
style: { display: "contents" },
dangerouslySetInnerHTML: { __html: html }
}) : null;
})() : props.fallback ?? null;
const markerAttributes = markerHydrateType === dynamicType ? void 0 : hydrateStrategy._a?.();
if (hydrateStrategy._t === "never" && !shouldPreserveServerHTML) return /* @__PURE__ */ jsx("div", {
ref: markerRef,
[hydrateIdAttribute]: gate.id,
[hydrateWhenAttribute]: markerHydrateType,
...markerAttributes,
children: props.fallback ?? null
});
return /* @__PURE__ */ jsx("div", {
ref: markerRef,
[hydrateIdAttribute]: gate.id,
[hydrateWhenAttribute]: markerHydrateType,
...markerAttributes,
children: /* @__PURE__ */ jsx(React.Suspense, {
fallback,
children: /* @__PURE__ */ jsx(HydrationGate, {
gate,
children: /* @__PURE__ */ jsx(HydratedBoundary, {
id: gate.id,
onHydrated: props.onHydrated,
onStrategyHydrated: (id) => {
markerElementRef.current?.removeAttribute(hydrateWhenAttribute);
hydrateStrategy._o?.(id);
},
children: props.children
})
})
})
});
}
//#endregion
export { GenericHydrate };
//# sourceMappingURL=GenericHydrate.js.map
{"version":3,"file":"GenericHydrate.js","names":[],"sources":["../../src/GenericHydrate.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { reactUse, useHydrated, useLayoutEffect } from '@tanstack/react-router'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport {\n hydrateIdAttribute,\n hydrateWhenAttribute,\n} from '@tanstack/start-client-core/hydration/constants'\nimport {\n createResolvedGate,\n getFallbackHtml,\n getOrCreateGate,\n onGateResolve,\n releaseGate,\n runHydrationStrategyCleanup,\n saveFallbackHtml,\n waitForHydrationPrefetchStrategy,\n} from '@tanstack/start-client-core/hydration/runtime'\nimport { listenForDelegatedHydrationIntent } from '@tanstack/start-client-core/hydration'\nimport type {\n HydrationRuntimeContext,\n HydrationStrategy,\n HydrationWhen,\n} from '@tanstack/start-client-core/hydration'\nimport type { HydrationGateRecord } from '@tanstack/start-client-core/hydration/runtime'\nimport type { HydrateProps, InternalHydrateProps } from './Hydrate'\n\ntype Gate = HydrationGateRecord & { promise: Promise<void> }\ntype PrefetchController = {\n abortController: AbortController\n hydrationRequested: boolean\n hydrationListeners: Set<() => void>\n hydrationResolvePending: boolean\n started: boolean\n promise?: Promise<void>\n cleanup?: () => void\n}\n\nconst dynamicType = 'dynamic'\nconst dynamicHydrateStrategy = {\n _t: dynamicType,\n _d: () => true,\n} satisfies HydrationStrategy<typeof dynamicType, false>\n\nfunction shouldDeferHydration(strategy: HydrationStrategy) {\n return strategy._d ? strategy._d() : strategy._t !== 'load'\n}\n\nfunction useLatest<T>(value: T) {\n const ref = React.useRef(value)\n ref.current = value\n return ref\n}\n\nfunction useHydrationGate(props: InternalHydrateProps) {\n const hydrated = useHydrated()\n const reactId = React.useId()\n const id = props.h ? `${props.h}${reactId}` : reactId\n const when = props.when\n const isDynamicHydrate = typeof when === 'function'\n const dynamicHydrateStrategyRef = React.useRef<HydrationStrategy | undefined>(\n undefined,\n )\n if (isDynamicHydrate) {\n dynamicHydrateStrategyRef.current ??=\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (isServer ?? typeof window === 'undefined')\n ? dynamicHydrateStrategy\n : when()\n }\n const hydrateStrategy = isDynamicHydrate\n ? dynamicHydrateStrategyRef.current!\n : when\n const markerHydrateType: HydrationWhen = isDynamicHydrate\n ? dynamicType\n : hydrateStrategy._t!\n const [prefetchError, setPrefetchError] = React.useState<unknown>()\n const latestRef = useLatest({\n prefetch: props.prefetch,\n preload: props.p,\n })\n const gateRef = React.useRef<HydrationGateRecord | undefined>(undefined)\n const markerElementRef = React.useRef<HTMLDivElement | null>(null)\n const shouldPreserveServerHTMLRef = React.useRef<boolean | undefined>(\n undefined,\n )\n const shouldDeferInitialHydrationRef = React.useRef<boolean | undefined>(\n undefined,\n )\n const didPrefetchRef = React.useRef(false)\n const prefetchControllerRef = React.useRef<PrefetchController | undefined>(\n undefined,\n )\n\n prefetchControllerRef.current ??= {\n abortController: new AbortController(),\n hydrationRequested: false,\n hydrationListeners: new Set<() => void>(),\n hydrationResolvePending: false,\n started: false,\n }\n\n shouldPreserveServerHTMLRef.current ??=\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (isServer ?? typeof window === 'undefined') || !hydrated\n shouldDeferInitialHydrationRef.current ??=\n !hydrated && shouldDeferHydration(hydrateStrategy)\n\n if (!gateRef.current) {\n gateRef.current =\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (isServer ?? typeof window === 'undefined')\n ? createResolvedGate(id, hydrateStrategy._t!)\n : getOrCreateGate(id, hydrateStrategy._t!)\n }\n\n gateRef.current.when = hydrateStrategy._t!\n\n if (\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n !(isServer ?? typeof window === 'undefined') &&\n hydrateStrategy._t !== 'never' &&\n (!shouldDeferInitialHydrationRef.current ||\n !shouldDeferHydration(hydrateStrategy))\n ) {\n gateRef.current.resolve()\n }\n\n const markerRef = React.useCallback(\n (element: HTMLDivElement | null) => {\n markerElementRef.current = element\n if (element) {\n if (\n hydrateStrategy._t === 'never' &&\n !shouldPreserveServerHTMLRef.current\n ) {\n element.replaceChildren()\n }\n saveFallbackHtml(id, element)\n }\n },\n [hydrateStrategy._t, id],\n )\n\n React.useEffect(() => {\n const gate = gateRef.current!\n return () => {\n const controller = prefetchControllerRef.current\n controller?.abortController.abort()\n controller?.cleanup?.()\n controller?.hydrationListeners.clear()\n releaseGate(gate)\n }\n }, [])\n\n React.useEffect(() => {\n if (\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (isServer ?? typeof window === 'undefined') ||\n !latestRef.current.prefetch\n ) {\n return\n }\n\n const controller = prefetchControllerRef.current!\n if (controller.started) return\n controller.started = true\n\n const onHydrate = (listener: () => void) => {\n if (controller.hydrationRequested) {\n listener()\n return () => {}\n }\n\n controller.hydrationListeners.add(listener)\n return () => {\n controller.hydrationListeners.delete(listener)\n }\n }\n\n const preload = () => latestRef.current.preload?.() ?? Promise.resolve()\n const prefetchInput = latestRef.current.prefetch\n\n if (typeof prefetchInput === 'function') {\n const promise = Promise.resolve()\n .then(() =>\n prefetchInput({\n element: markerElementRef.current,\n signal: controller.abortController.signal,\n preload,\n waitFor: (strategy) =>\n waitForHydrationPrefetchStrategy(strategy, {\n element: markerElementRef.current,\n signal: controller.abortController.signal,\n onHydrate,\n }),\n }),\n )\n .then(() => undefined)\n\n controller.promise = promise\n promise.catch((error) => {\n if (!controller.abortController.signal.aborted) {\n setPrefetchError(error)\n }\n })\n return\n }\n\n if (!latestRef.current.preload) return\n\n const prefetch = () => {\n if (didPrefetchRef.current) return\n didPrefetchRef.current = true\n void preload()\n }\n\n controller.cleanup = runHydrationStrategyCleanup(\n prefetchInput._s?.({\n element: markerElementRef.current,\n prefetch,\n }),\n )\n }, [hydrateStrategy, latestRef])\n\n useLayoutEffect(() => {\n const gate = gateRef.current!\n if (\n !shouldDeferInitialHydrationRef.current ||\n hydrateStrategy._t === 'never'\n ) {\n return\n }\n\n if (gate.resolved) {\n return\n }\n\n const cleanups: Array<() => void> = []\n let removeResolveListener = () => {}\n let disposed = false\n const resolveGate = gate.resolve\n\n const cleanup = () => {\n if (disposed) return\n disposed = true\n if (gate.resolve === requestHydration) {\n gate.resolve = resolveGate\n }\n removeResolveListener()\n cleanups.forEach((fn) => fn())\n }\n\n const addCleanup = (fn: void | (() => void)) => {\n if (!fn) return\n if (disposed || gate.resolved) {\n fn()\n return\n }\n cleanups.push(fn)\n }\n\n const requestHydration = () => {\n const controller = prefetchControllerRef.current!\n if (!controller.hydrationRequested) {\n controller.hydrationRequested = true\n controller.hydrationListeners.forEach((listener) => listener())\n controller.hydrationListeners.clear()\n }\n\n if (!controller.promise) {\n resolveGate()\n return\n }\n if (controller.hydrationResolvePending) return\n controller.hydrationResolvePending = true\n\n controller.promise.then(\n () => resolveGate(),\n (error) => {\n if (!controller.abortController.signal.aborted) {\n setPrefetchError(error)\n }\n },\n )\n }\n\n gate.resolve = requestHydration\n removeResolveListener = onGateResolve(gate, cleanup)\n\n const context: HydrationRuntimeContext = {\n element: markerElementRef.current,\n gate,\n }\n addCleanup(runHydrationStrategyCleanup(hydrateStrategy._s?.(context)))\n\n if (hydrateStrategy._t !== 'interaction') {\n addCleanup(\n runHydrationStrategyCleanup(\n markerElementRef.current\n ? listenForDelegatedHydrationIntent(\n markerElementRef.current,\n context,\n )\n : undefined,\n ),\n )\n }\n\n return cleanup\n }, [hydrateStrategy, latestRef])\n\n return {\n gate: gateRef.current,\n markerRef,\n markerElementRef,\n hydrateStrategy,\n markerHydrateType,\n prefetchError,\n shouldPreserveServerHTML: shouldPreserveServerHTMLRef.current,\n }\n}\n\nfunction HydrationGate(props: { gate: Gate; children: React.ReactNode }) {\n if (\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n isServer ??\n typeof window === 'undefined'\n ) {\n return props.children as React.JSX.Element\n }\n\n if (props.gate.resolved) {\n return props.children as React.JSX.Element\n }\n\n if (!reactUse) {\n throw props.gate.promise\n }\n\n reactUse(props.gate.promise)\n\n return props.children as React.JSX.Element\n}\n\nfunction HydratedBoundary(props: {\n id: string\n onHydrated?: () => void\n onStrategyHydrated?: (id: string) => void\n children: React.ReactNode\n}) {\n const { id, onHydrated, onStrategyHydrated } = props\n const didHydrateRef = React.useRef(false)\n\n React.useEffect(() => {\n if (didHydrateRef.current) return\n didHydrateRef.current = true\n onHydrated?.()\n onStrategyHydrated?.(id)\n }, [id, onHydrated, onStrategyHydrated])\n\n return props.children as React.JSX.Element\n}\n\nexport function GenericHydrate(props: HydrateProps): React.JSX.Element {\n const internalProps = props as InternalHydrateProps\n const {\n gate,\n hydrateStrategy,\n markerHydrateType,\n markerElementRef,\n markerRef,\n prefetchError,\n shouldPreserveServerHTML,\n } = useHydrationGate(internalProps)\n if (prefetchError) throw prefetchError\n\n const fallback = shouldPreserveServerHTML\n ? (() => {\n const html = getFallbackHtml(gate.id)\n return html ? (\n <div\n style={{ display: 'contents' }}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n ) : null\n })()\n : (props.fallback ?? null)\n const markerAttributes =\n markerHydrateType === dynamicType ? undefined : hydrateStrategy._a?.()\n\n const hydrateType = hydrateStrategy._t!\n\n if (hydrateType === 'never' && !shouldPreserveServerHTML) {\n return (\n <div\n ref={markerRef}\n {...{\n [hydrateIdAttribute]: gate.id,\n [hydrateWhenAttribute]: markerHydrateType,\n ...markerAttributes,\n }}\n >\n {props.fallback ?? null}\n </div>\n )\n }\n\n return (\n <div\n ref={markerRef}\n {...{\n [hydrateIdAttribute]: gate.id,\n [hydrateWhenAttribute]: markerHydrateType,\n ...markerAttributes,\n }}\n >\n <React.Suspense fallback={fallback}>\n <HydrationGate gate={gate}>\n <HydratedBoundary\n id={gate.id}\n onHydrated={props.onHydrated}\n onStrategyHydrated={(id) => {\n markerElementRef.current?.removeAttribute(hydrateWhenAttribute)\n hydrateStrategy._o?.(id)\n }}\n >\n {props.children}\n </HydratedBoundary>\n </HydrationGate>\n </React.Suspense>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;AAwCA,IAAM,cAAc;AACpB,IAAM,yBAAyB;CAC7B,IAAI;CACJ,UAAU;CACX;AAED,SAAS,qBAAqB,UAA6B;AACzD,QAAO,SAAS,KAAK,SAAS,IAAI,GAAG,SAAS,OAAO;;AAGvD,SAAS,UAAa,OAAU;CAC9B,MAAM,MAAM,MAAM,OAAO,MAAM;AAC/B,KAAI,UAAU;AACd,QAAO;;AAGT,SAAS,iBAAiB,OAA6B;CACrD,MAAM,WAAW,aAAa;CAC9B,MAAM,UAAU,MAAM,OAAO;CAC7B,MAAM,KAAK,MAAM,IAAI,GAAG,MAAM,IAAI,YAAY;CAC9C,MAAM,OAAO,MAAM;CACnB,MAAM,mBAAmB,OAAO,SAAS;CACzC,MAAM,4BAA4B,MAAM,OACtC,KAAA,EACD;AACD,KAAI,iBACF,2BAA0B,YAEvB,YAAY,OAAO,WAAW,cAC3B,yBACA,MAAM;CAEd,MAAM,kBAAkB,mBACpB,0BAA0B,UAC1B;CACJ,MAAM,oBAAmC,mBACrC,cACA,gBAAgB;CACpB,MAAM,CAAC,eAAe,oBAAoB,MAAM,UAAmB;CACnE,MAAM,YAAY,UAAU;EAC1B,UAAU,MAAM;EAChB,SAAS,MAAM;EAChB,CAAC;CACF,MAAM,UAAU,MAAM,OAAwC,KAAA,EAAU;CACxE,MAAM,mBAAmB,MAAM,OAA8B,KAAK;CAClE,MAAM,8BAA8B,MAAM,OACxC,KAAA,EACD;CACD,MAAM,iCAAiC,MAAM,OAC3C,KAAA,EACD;CACD,MAAM,iBAAiB,MAAM,OAAO,MAAM;CAC1C,MAAM,wBAAwB,MAAM,OAClC,KAAA,EACD;AAED,uBAAsB,YAAY;EAChC,iBAAiB,IAAI,iBAAiB;EACtC,oBAAoB;EACpB,oCAAoB,IAAI,KAAiB;EACzC,yBAAyB;EACzB,SAAS;EACV;AAED,6BAA4B,aAEzB,YAAY,OAAO,WAAW,gBAAgB,CAAC;AAClD,gCAA+B,YAC7B,CAAC,YAAY,qBAAqB,gBAAgB;AAEpD,KAAI,CAAC,QAAQ,QACX,SAAQ,UAEL,YAAY,OAAO,WAAW,cAC3B,mBAAmB,IAAI,gBAAgB,GAAI,GAC3C,gBAAgB,IAAI,gBAAgB,GAAI;AAGhD,SAAQ,QAAQ,OAAO,gBAAgB;AAEvC,KAEE,EAAE,YAAY,OAAO,WAAW,gBAChC,gBAAgB,OAAO,YACtB,CAAC,+BAA+B,WAC/B,CAAC,qBAAqB,gBAAgB,EAExC,SAAQ,QAAQ,SAAS;CAG3B,MAAM,YAAY,MAAM,aACrB,YAAmC;AAClC,mBAAiB,UAAU;AAC3B,MAAI,SAAS;AACX,OACE,gBAAgB,OAAO,WACvB,CAAC,4BAA4B,QAE7B,SAAQ,iBAAiB;AAE3B,oBAAiB,IAAI,QAAQ;;IAGjC,CAAC,gBAAgB,IAAI,GAAG,CACzB;AAED,OAAM,gBAAgB;EACpB,MAAM,OAAO,QAAQ;AACrB,eAAa;GACX,MAAM,aAAa,sBAAsB;AACzC,eAAY,gBAAgB,OAAO;AACnC,eAAY,WAAW;AACvB,eAAY,mBAAmB,OAAO;AACtC,eAAY,KAAK;;IAElB,EAAE,CAAC;AAEN,OAAM,gBAAgB;AACpB,OAEG,YAAY,OAAO,WAAW,gBAC/B,CAAC,UAAU,QAAQ,SAEnB;EAGF,MAAM,aAAa,sBAAsB;AACzC,MAAI,WAAW,QAAS;AACxB,aAAW,UAAU;EAErB,MAAM,aAAa,aAAyB;AAC1C,OAAI,WAAW,oBAAoB;AACjC,cAAU;AACV,iBAAa;;AAGf,cAAW,mBAAmB,IAAI,SAAS;AAC3C,gBAAa;AACX,eAAW,mBAAmB,OAAO,SAAS;;;EAIlD,MAAM,gBAAgB,UAAU,QAAQ,WAAW,IAAI,QAAQ,SAAS;EACxE,MAAM,gBAAgB,UAAU,QAAQ;AAExC,MAAI,OAAO,kBAAkB,YAAY;GACvC,MAAM,UAAU,QAAQ,SAAS,CAC9B,WACC,cAAc;IACZ,SAAS,iBAAiB;IAC1B,QAAQ,WAAW,gBAAgB;IACnC;IACA,UAAU,aACR,iCAAiC,UAAU;KACzC,SAAS,iBAAiB;KAC1B,QAAQ,WAAW,gBAAgB;KACnC;KACD,CAAC;IACL,CAAC,CACH,CACA,WAAW,KAAA,EAAU;AAExB,cAAW,UAAU;AACrB,WAAQ,OAAO,UAAU;AACvB,QAAI,CAAC,WAAW,gBAAgB,OAAO,QACrC,kBAAiB,MAAM;KAEzB;AACF;;AAGF,MAAI,CAAC,UAAU,QAAQ,QAAS;EAEhC,MAAM,iBAAiB;AACrB,OAAI,eAAe,QAAS;AAC5B,kBAAe,UAAU;AACpB,YAAS;;AAGhB,aAAW,UAAU,4BACnB,cAAc,KAAK;GACjB,SAAS,iBAAiB;GAC1B;GACD,CAAC,CACH;IACA,CAAC,iBAAiB,UAAU,CAAC;AAEhC,uBAAsB;EACpB,MAAM,OAAO,QAAQ;AACrB,MACE,CAAC,+BAA+B,WAChC,gBAAgB,OAAO,QAEvB;AAGF,MAAI,KAAK,SACP;EAGF,MAAM,WAA8B,EAAE;EACtC,IAAI,8BAA8B;EAClC,IAAI,WAAW;EACf,MAAM,cAAc,KAAK;EAEzB,MAAM,gBAAgB;AACpB,OAAI,SAAU;AACd,cAAW;AACX,OAAI,KAAK,YAAY,iBACnB,MAAK,UAAU;AAEjB,0BAAuB;AACvB,YAAS,SAAS,OAAO,IAAI,CAAC;;EAGhC,MAAM,cAAc,OAA4B;AAC9C,OAAI,CAAC,GAAI;AACT,OAAI,YAAY,KAAK,UAAU;AAC7B,QAAI;AACJ;;AAEF,YAAS,KAAK,GAAG;;EAGnB,MAAM,yBAAyB;GAC7B,MAAM,aAAa,sBAAsB;AACzC,OAAI,CAAC,WAAW,oBAAoB;AAClC,eAAW,qBAAqB;AAChC,eAAW,mBAAmB,SAAS,aAAa,UAAU,CAAC;AAC/D,eAAW,mBAAmB,OAAO;;AAGvC,OAAI,CAAC,WAAW,SAAS;AACvB,iBAAa;AACb;;AAEF,OAAI,WAAW,wBAAyB;AACxC,cAAW,0BAA0B;AAErC,cAAW,QAAQ,WACX,aAAa,GAClB,UAAU;AACT,QAAI,CAAC,WAAW,gBAAgB,OAAO,QACrC,kBAAiB,MAAM;KAG5B;;AAGH,OAAK,UAAU;AACf,0BAAwB,cAAc,MAAM,QAAQ;EAEpD,MAAM,UAAmC;GACvC,SAAS,iBAAiB;GAC1B;GACD;AACD,aAAW,4BAA4B,gBAAgB,KAAK,QAAQ,CAAC,CAAC;AAEtE,MAAI,gBAAgB,OAAO,cACzB,YACE,4BACE,iBAAiB,UACb,kCACE,iBAAiB,SACjB,QACD,GACD,KAAA,EACL,CACF;AAGH,SAAO;IACN,CAAC,iBAAiB,UAAU,CAAC;AAEhC,QAAO;EACL,MAAM,QAAQ;EACd;EACA;EACA;EACA;EACA;EACA,0BAA0B,4BAA4B;EACvD;;AAGH,SAAS,cAAc,OAAkD;AACvE,KAEE,YACA,OAAO,WAAW,YAElB,QAAO,MAAM;AAGf,KAAI,MAAM,KAAK,SACb,QAAO,MAAM;AAGf,KAAI,CAAC,SACH,OAAM,MAAM,KAAK;AAGnB,UAAS,MAAM,KAAK,QAAQ;AAE5B,QAAO,MAAM;;AAGf,SAAS,iBAAiB,OAKvB;CACD,MAAM,EAAE,IAAI,YAAY,uBAAuB;CAC/C,MAAM,gBAAgB,MAAM,OAAO,MAAM;AAEzC,OAAM,gBAAgB;AACpB,MAAI,cAAc,QAAS;AAC3B,gBAAc,UAAU;AACxB,gBAAc;AACd,uBAAqB,GAAG;IACvB;EAAC;EAAI;EAAY;EAAmB,CAAC;AAExC,QAAO,MAAM;;AAGf,SAAgB,eAAe,OAAwC;CAErE,MAAM,EACJ,MACA,iBACA,mBACA,kBACA,WACA,eACA,6BACE,iBATkB,MASa;AACnC,KAAI,cAAe,OAAM;CAEzB,MAAM,WAAW,kCACN;EACL,MAAM,OAAO,gBAAgB,KAAK,GAAG;AACrC,SAAO,OACL,oBAAC,OAAD;GACE,OAAO,EAAE,SAAS,YAAY;GAC9B,yBAAyB,EAAE,QAAQ,MAAM;GACzC,CAAA,GACA;KACF,GACH,MAAM,YAAY;CACvB,MAAM,mBACJ,sBAAsB,cAAc,KAAA,IAAY,gBAAgB,MAAM;AAIxE,KAFoB,gBAAgB,OAEhB,WAAW,CAAC,yBAC9B,QACE,oBAAC,OAAD;EACE,KAAK;GAEF,qBAAqB,KAAK;GAC1B,uBAAuB;EACxB,GAAG;YAGJ,MAAM,YAAY;EACf,CAAA;AAIV,QACE,oBAAC,OAAD;EACE,KAAK;GAEF,qBAAqB,KAAK;GAC1B,uBAAuB;EACxB,GAAG;YAGL,oBAAC,MAAM,UAAP;GAA0B;aACxB,oBAAC,eAAD;IAAqB;cACnB,oBAAC,kBAAD;KACE,IAAI,KAAK;KACT,YAAY,MAAM;KAClB,qBAAqB,OAAO;AAC1B,uBAAiB,SAAS,gBAAgB,qBAAqB;AAC/D,sBAAgB,KAAK,GAAG;;eAGzB,MAAM;KACU,CAAA;IACL,CAAA;GACD,CAAA;EACb,CAAA"}
import { HydrationStrategy as CoreHydrationStrategy, HydrationPrefetchFunction, HydrationPrefetchStrategy, HydrationWhen } from '@tanstack/start-client-core/hydration';
import * as React from 'react';
export type { HydrationInteractionEvent, HydrationInteractionEvents, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationWhen, } from '@tanstack/start-client-core/hydration';
export type ReactHydrationStrategy<TWhen extends HydrationWhen = HydrationWhen, TCanPrefetch extends boolean = boolean> = CoreHydrationStrategy<TWhen, TCanPrefetch> & {
_h: (this: ReactHydrationStrategy, props: HydrateProps) => React.JSX.Element;
};
export type HydrationStrategy<TWhen extends HydrationWhen = HydrationWhen, TCanPrefetch extends boolean = boolean> = ReactHydrationStrategy<TWhen, TCanPrefetch>;
export type HydrateWhen = ReactHydrationStrategy | (() => ReactHydrationStrategy);
type HydrateCommonOptions = {
when: HydrateWhen;
fallback?: React.ReactNode;
onHydrated?: () => void;
};
export type HydrateOptions = (HydrateCommonOptions & {
prefetch?: never;
split?: boolean;
}) | (HydrateCommonOptions & {
prefetch: HydrationPrefetchStrategy;
split?: true;
}) | (HydrateCommonOptions & {
prefetch: HydrationPrefetchFunction;
split?: boolean;
});
export type HydrateProps = HydrateOptions & {
children: React.ReactNode;
};
export type InternalHydrateProps = HydrateProps & {
h?: string;
p?: () => Promise<void>;
};
export declare function Hydrate(props: HydrateProps): React.JSX.Element;
"use client";
import { jsx } from "react/jsx-runtime";
import * as React from "react";
import { isServer } from "@tanstack/router-core/isServer";
//#region src/Hydrate.tsx
var dynamicType = "dynamic";
var hydrateIdAttribute = "data-ts-hydrate-id";
var hydrateWhenAttribute = "data-ts-hydrate-when";
/* @__NO_SIDE_EFFECTS__ */
function ServerDynamicHydrate(props) {
const internalProps = props;
const reactId = React.useId();
const id = internalProps.h ? `${internalProps.h}${reactId}` : reactId;
return /* @__PURE__ */ jsx("div", {
[hydrateIdAttribute]: id,
[hydrateWhenAttribute]: dynamicType,
children: /* @__PURE__ */ jsx(React.Suspense, {
fallback: props.fallback ?? null,
children: props.children
})
});
}
/* @__NO_SIDE_EFFECTS__ */
function Hydrate(props) {
if (typeof props.when === "function") {
if (isServer ?? typeof window === "undefined") return /* @__PURE__ */ jsx(ServerDynamicHydrate, { ...props });
return props.when()._h(props);
}
return props.when._h(props);
}
//#endregion
export { Hydrate };
//# sourceMappingURL=Hydrate.js.map
{"version":3,"file":"Hydrate.js","names":[],"sources":["../../src/Hydrate.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { isServer } from '@tanstack/router-core/isServer'\nimport type {\n HydrationStrategy as CoreHydrationStrategy,\n HydrationPrefetchFunction,\n HydrationPrefetchStrategy,\n HydrationWhen,\n} from '@tanstack/start-client-core/hydration'\n\nexport type {\n HydrationInteractionEvent,\n HydrationInteractionEvents,\n HydrationPrefetchContext,\n HydrationPrefetchFunction,\n HydrationPrefetchStrategy,\n HydrationPrefetchWaitReason,\n HydrationWhen,\n} from '@tanstack/start-client-core/hydration'\n\nexport type ReactHydrationStrategy<\n TWhen extends HydrationWhen = HydrationWhen,\n TCanPrefetch extends boolean = boolean,\n> = CoreHydrationStrategy<TWhen, TCanPrefetch> & {\n _h: (this: ReactHydrationStrategy, props: HydrateProps) => React.JSX.Element\n}\n\nexport type HydrationStrategy<\n TWhen extends HydrationWhen = HydrationWhen,\n TCanPrefetch extends boolean = boolean,\n> = ReactHydrationStrategy<TWhen, TCanPrefetch>\n\nexport type HydrateWhen =\n | ReactHydrationStrategy\n | (() => ReactHydrationStrategy)\n\ntype HydrateCommonOptions = {\n when: HydrateWhen\n fallback?: React.ReactNode\n onHydrated?: () => void\n}\n\nexport type HydrateOptions =\n | (HydrateCommonOptions & {\n prefetch?: never\n split?: boolean\n })\n | (HydrateCommonOptions & {\n prefetch: HydrationPrefetchStrategy\n split?: true\n })\n | (HydrateCommonOptions & {\n prefetch: HydrationPrefetchFunction\n split?: boolean\n })\n\nexport type HydrateProps = HydrateOptions & {\n children: React.ReactNode\n}\n\nexport type InternalHydrateProps = HydrateProps & {\n h?: string\n p?: () => Promise<void>\n}\n\nconst dynamicType = 'dynamic'\nconst hydrateIdAttribute = 'data-ts-hydrate-id'\nconst hydrateWhenAttribute = 'data-ts-hydrate-when'\n\n/* @__NO_SIDE_EFFECTS__ */\nfunction ServerDynamicHydrate(props: HydrateProps): React.JSX.Element {\n const internalProps = props as InternalHydrateProps\n const reactId = React.useId()\n const id = internalProps.h ? `${internalProps.h}${reactId}` : reactId\n\n return (\n <div\n {...{\n [hydrateIdAttribute]: id,\n [hydrateWhenAttribute]: dynamicType,\n }}\n >\n <React.Suspense fallback={props.fallback ?? null}>\n {props.children}\n </React.Suspense>\n </div>\n )\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function Hydrate(props: HydrateProps): React.JSX.Element {\n if (typeof props.when === 'function') {\n if (\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n isServer ??\n typeof window === 'undefined'\n ) {\n return <ServerDynamicHydrate {...props} />\n }\n\n return props.when()._h(props)\n }\n\n return props.when._h(props)\n}\n"],"mappings":";;;;;AAmEA,IAAM,cAAc;AACpB,IAAM,qBAAqB;AAC3B,IAAM,uBAAuB;;AAG7B,SAAS,qBAAqB,OAAwC;CACpE,MAAM,gBAAgB;CACtB,MAAM,UAAU,MAAM,OAAO;CAC7B,MAAM,KAAK,cAAc,IAAI,GAAG,cAAc,IAAI,YAAY;AAE9D,QACE,oBAAC,OAAD;GAEK,qBAAqB;GACrB,uBAAuB;YAG1B,oBAAC,MAAM,UAAP;GAAgB,UAAU,MAAM,YAAY;aACzC,MAAM;GACQ,CAAA;EACb,CAAA;;;AAKV,SAAgB,QAAQ,OAAwC;AAC9D,KAAI,OAAO,MAAM,SAAS,YAAY;AACpC,MAEE,YACA,OAAO,WAAW,YAElB,QAAO,oBAAC,sBAAD,EAAsB,GAAI,OAAS,CAAA;AAG5C,SAAO,MAAM,MAAM,CAAC,GAAG,MAAM;;AAG/B,QAAO,MAAM,KAAK,GAAG,MAAM"}
export { condition, interaction, media } from './hydration/generic.js';
export { idle } from './hydration/idle.js';
export { load } from './hydration/load.js';
export { never } from './hydration/never.js';
export { visible } from './hydration/visible.js';
export type { HydrationCondition, HydrationInteractionEvent, HydrationInteractionEvents, IdleHydrationOptions, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchWhen, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationStrategyTypes, HydrationWhen, VisibleHydrationOptions, } from '@tanstack/start-client-core/hydration';
export type { HydrationStrategy, ReactHydrationStrategy } from './Hydrate.js';
"use client";
import { condition, interaction, media } from "./hydration/generic.js";
import { idle } from "./hydration/idle.js";
import { load } from "./hydration/load.js";
import { never } from "./hydration/never.js";
import { visible } from "./hydration/visible.js";
export { condition, idle, interaction, load, media, never, visible };
import { HydrationCondition, HydrationInteractionEvents, HydrationPrefetchStrategy } from '@tanstack/start-client-core/hydration';
import { ReactHydrationStrategy } from '../Hydrate.js';
export declare function media(query: string): ReactHydrationStrategy<'media', true> & HydrationPrefetchStrategy<'media'>;
export declare function condition(condition: HydrationCondition): ReactHydrationStrategy<'condition', false>;
export declare function interaction(options?: {
events?: HydrationInteractionEvents;
}): ReactHydrationStrategy<'interaction', true> & HydrationPrefetchStrategy<'interaction'>;
"use client";
import { GenericHydrate } from "../GenericHydrate.js";
import { condition, interaction, media, withHydrationRenderer } from "@tanstack/start-client-core/hydration";
//#region src/hydration/generic.ts
/* @__NO_SIDE_EFFECTS__ */
function media$1(query) {
return /* @__PURE__ */ withHydrationRenderer(media(query), GenericHydrate);
}
/* @__NO_SIDE_EFFECTS__ */
function condition$1(condition$2) {
return /* @__PURE__ */ withHydrationRenderer(condition(condition$2), GenericHydrate);
}
/* @__NO_SIDE_EFFECTS__ */
function interaction$1(options) {
return /* @__PURE__ */ withHydrationRenderer(interaction(options), GenericHydrate);
}
//#endregion
export { condition$1 as condition, interaction$1 as interaction, media$1 as media };
//# sourceMappingURL=generic.js.map
{"version":3,"file":"generic.js","names":[],"sources":["../../../src/hydration/generic.ts"],"sourcesContent":["'use client'\n\nimport {\n condition as coreCondition,\n interaction as coreInteraction,\n media as coreMedia,\n withHydrationRenderer,\n} from '@tanstack/start-client-core/hydration'\nimport { GenericHydrate } from '../GenericHydrate'\nimport type {\n HydrationCondition,\n HydrationInteractionEvents,\n HydrationPrefetchStrategy,\n} from '@tanstack/start-client-core/hydration'\nimport type { ReactHydrationStrategy } from '../Hydrate'\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function media(\n query: string,\n): ReactHydrationStrategy<'media', true> & HydrationPrefetchStrategy<'media'> {\n return /* @__PURE__ */ withHydrationRenderer(coreMedia(query), GenericHydrate)\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function condition(\n condition: HydrationCondition,\n): ReactHydrationStrategy<'condition', false> {\n return /* @__PURE__ */ withHydrationRenderer(\n coreCondition(condition),\n GenericHydrate,\n )\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function interaction(options?: {\n events?: HydrationInteractionEvents\n}): ReactHydrationStrategy<'interaction', true> &\n HydrationPrefetchStrategy<'interaction'> {\n return /* @__PURE__ */ withHydrationRenderer(\n coreInteraction(options),\n GenericHydrate,\n )\n}\n"],"mappings":";;;;;AAiBA,SAAgB,QACd,OAC4E;AAC5E,QAAuB,sCAAsB,MAAU,MAAM,EAAE,eAAe;;;AAIhF,SAAgB,YACd,aAC4C;AAC5C,QAAuB,sCACrB,UAAc,YAAU,EACxB,eACD;;;AAIH,SAAgB,cAAY,SAGe;AACzC,QAAuB,sCACrB,YAAgB,QAAQ,EACxB,eACD"}
import { HydrationPrefetchStrategy, IdleHydrationOptions } from '@tanstack/start-client-core/hydration';
import { ReactHydrationStrategy } from '../Hydrate.js';
export declare function idle(options?: IdleHydrationOptions): ReactHydrationStrategy<'idle', true> & HydrationPrefetchStrategy<'idle'>;
"use client";
import { GenericHydrate } from "../GenericHydrate.js";
import { idle, withHydrationRenderer } from "@tanstack/start-client-core/hydration";
//#region src/hydration/idle.ts
/* @__NO_SIDE_EFFECTS__ */
function idle$1(options = {}) {
return /* @__PURE__ */ withHydrationRenderer(idle(options), GenericHydrate);
}
//#endregion
export { idle$1 as idle };
//# sourceMappingURL=idle.js.map
{"version":3,"file":"idle.js","names":[],"sources":["../../../src/hydration/idle.ts"],"sourcesContent":["'use client'\n\nimport {\n idle as coreIdle,\n withHydrationRenderer,\n} from '@tanstack/start-client-core/hydration'\nimport { GenericHydrate } from '../GenericHydrate'\nimport type {\n HydrationPrefetchStrategy,\n IdleHydrationOptions,\n} from '@tanstack/start-client-core/hydration'\nimport type { ReactHydrationStrategy } from '../Hydrate'\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function idle(\n options: IdleHydrationOptions = {},\n): ReactHydrationStrategy<'idle', true> & HydrationPrefetchStrategy<'idle'> {\n return /* @__PURE__ */ withHydrationRenderer(\n coreIdle(options),\n GenericHydrate,\n )\n}\n"],"mappings":";;;;;AAcA,SAAgB,OACd,UAAgC,EAAE,EACwC;AAC1E,QAAuB,sCACrB,KAAS,QAAQ,EACjB,eACD"}
import { HydrationPrefetchStrategy } from '@tanstack/start-client-core/hydration';
import { HydrateProps, ReactHydrationStrategy } from '../Hydrate.js';
import * as React from 'react';
export declare function LoadHydrate(props: HydrateProps): React.JSX.Element;
export declare function load(): ReactHydrationStrategy<'load', true> & HydrationPrefetchStrategy<'load'>;
"use client";
import { jsx } from "react/jsx-runtime";
import * as React from "react";
import { load, withHydrationRenderer } from "@tanstack/start-client-core/hydration";
//#region src/hydration/load.tsx
function HydratedBoundary(props) {
const { onHydrated, children } = props;
const didHydrateRef = React.useRef(false);
React.useEffect(() => {
if (didHydrateRef.current) return;
didHydrateRef.current = true;
onHydrated?.();
}, [onHydrated]);
return children;
}
function LoadHydrate(props) {
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(React.Suspense, {
fallback: props.fallback ?? null,
children: /* @__PURE__ */ jsx(HydratedBoundary, {
onHydrated: props.onHydrated,
children: props.children
})
}) });
}
var loadStrategy = /* @__PURE__ */ withHydrationRenderer(load(), LoadHydrate);
/* @__NO_SIDE_EFFECTS__ */
function load$1() {
return loadStrategy;
}
//#endregion
export { load$1 as load };
//# sourceMappingURL=load.js.map
{"version":3,"file":"load.js","names":[],"sources":["../../../src/hydration/load.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport {\n load as coreLoad,\n withHydrationRenderer,\n} from '@tanstack/start-client-core/hydration'\nimport type { HydrationPrefetchStrategy } from '@tanstack/start-client-core/hydration'\nimport type { HydrateProps, ReactHydrationStrategy } from '../Hydrate'\n\nfunction HydratedBoundary(props: {\n onHydrated?: () => void\n children: React.ReactNode\n}) {\n const { onHydrated, children } = props\n const didHydrateRef = React.useRef(false)\n\n React.useEffect(() => {\n if (didHydrateRef.current) return\n didHydrateRef.current = true\n onHydrated?.()\n }, [onHydrated])\n\n return children as React.JSX.Element\n}\n\nexport function LoadHydrate(props: HydrateProps): React.JSX.Element {\n return (\n <div>\n <React.Suspense fallback={props.fallback ?? null}>\n <HydratedBoundary onHydrated={props.onHydrated}>\n {props.children}\n </HydratedBoundary>\n </React.Suspense>\n </div>\n )\n}\n\nconst loadStrategy = /* @__PURE__ */ withHydrationRenderer(\n coreLoad(),\n LoadHydrate,\n) as ReactHydrationStrategy<'load', true> & HydrationPrefetchStrategy<'load'>\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function load(): ReactHydrationStrategy<'load', true> &\n HydrationPrefetchStrategy<'load'> {\n return loadStrategy\n}\n"],"mappings":";;;;;AAWA,SAAS,iBAAiB,OAGvB;CACD,MAAM,EAAE,YAAY,aAAa;CACjC,MAAM,gBAAgB,MAAM,OAAO,MAAM;AAEzC,OAAM,gBAAgB;AACpB,MAAI,cAAc,QAAS;AAC3B,gBAAc,UAAU;AACxB,gBAAc;IACb,CAAC,WAAW,CAAC;AAEhB,QAAO;;AAGT,SAAgB,YAAY,OAAwC;AAClE,QACE,oBAAC,OAAD,EAAA,UACE,oBAAC,MAAM,UAAP;EAAgB,UAAU,MAAM,YAAY;YAC1C,oBAAC,kBAAD;GAAkB,YAAY,MAAM;aACjC,MAAM;GACU,CAAA;EACJ,CAAA,EACb,CAAA;;AAIV,IAAM,eAA+B,sCACnC,MAAU,EACV,YACD;;AAGD,SAAgB,SACoB;AAClC,QAAO"}
import { HydrateProps, ReactHydrationStrategy } from '../Hydrate.js';
import * as React from 'react';
export declare function NeverHydrate(props: HydrateProps): React.JSX.Element;
export declare function never(): ReactHydrationStrategy<'never', false>;
"use client";
import { reactUse, useHydrated } from "@tanstack/react-router";
import { jsx } from "react/jsx-runtime";
import * as React from "react";
import { isServer } from "@tanstack/router-core/isServer";
import { never, withHydrationRenderer } from "@tanstack/start-client-core/hydration";
import { hydrateIdAttribute, hydrateWhenAttribute } from "@tanstack/start-client-core/hydration/constants";
import { getFallbackHtml, saveFallbackHtml } from "@tanstack/start-client-core/hydration/runtime";
//#region src/hydration/never.tsx
var neverType = "never";
var neverPromise = new Promise(() => {});
function NeverGate(props) {
if (isServer ?? typeof window === "undefined") return props.children;
if (!reactUse) throw neverPromise;
reactUse(neverPromise);
return props.children;
}
function NeverHydrate(props) {
const internalProps = props;
const hydrated = useHydrated();
const reactId = React.useId();
const id = internalProps.h ? `${internalProps.h}${reactId}` : reactId;
const shouldPreserveServerHTMLRef = React.useRef(void 0);
shouldPreserveServerHTMLRef.current ??= (isServer ?? typeof window === "undefined") || !hydrated;
const markerProps = {
ref: React.useCallback((element) => {
if (!element) return;
if (!shouldPreserveServerHTMLRef.current) element.replaceChildren();
else saveFallbackHtml(id, element);
}, [id]),
[hydrateIdAttribute]: id,
[hydrateWhenAttribute]: neverType
};
const fallback = (() => {
const html = getFallbackHtml(id);
return html ? /* @__PURE__ */ jsx("div", {
style: { display: "contents" },
dangerouslySetInnerHTML: { __html: html }
}) : props.fallback ?? null;
})();
return /* @__PURE__ */ jsx("div", {
...markerProps,
children: /* @__PURE__ */ jsx(React.Suspense, {
fallback,
children: /* @__PURE__ */ jsx(NeverGate, { children: props.children })
})
});
}
/* @__NO_SIDE_EFFECTS__ */
function never$1() {
return /* @__PURE__ */ withHydrationRenderer(never(), NeverHydrate);
}
//#endregion
export { never$1 as never };
//# sourceMappingURL=never.js.map
{"version":3,"file":"never.js","names":[],"sources":["../../../src/hydration/never.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { reactUse, useHydrated } from '@tanstack/react-router'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport {\n never as coreNever,\n withHydrationRenderer,\n} from '@tanstack/start-client-core/hydration'\nimport {\n hydrateIdAttribute,\n hydrateWhenAttribute,\n} from '@tanstack/start-client-core/hydration/constants'\nimport {\n getFallbackHtml,\n saveFallbackHtml,\n} from '@tanstack/start-client-core/hydration/runtime'\nimport type {\n HydrateProps,\n InternalHydrateProps,\n ReactHydrationStrategy,\n} from '../Hydrate'\n\nconst neverType = 'never'\nconst neverPromise = new Promise<void>(() => {})\n\nfunction NeverGate(props: { children: React.ReactNode }) {\n if (\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n isServer ??\n typeof window === 'undefined'\n ) {\n return props.children as React.JSX.Element\n }\n\n if (!reactUse) {\n throw neverPromise\n }\n\n reactUse(neverPromise)\n\n return props.children as React.JSX.Element\n}\n\nexport function NeverHydrate(props: HydrateProps): React.JSX.Element {\n const internalProps = props as InternalHydrateProps\n const hydrated = useHydrated()\n const reactId = React.useId()\n const id = internalProps.h ? `${internalProps.h}${reactId}` : reactId\n const shouldPreserveServerHTMLRef = React.useRef<boolean | undefined>(\n undefined,\n )\n shouldPreserveServerHTMLRef.current ??=\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n (isServer ?? typeof window === 'undefined') || !hydrated\n const markerRef = React.useCallback(\n (element: HTMLDivElement | null) => {\n if (!element) return\n if (!shouldPreserveServerHTMLRef.current) {\n element.replaceChildren()\n } else {\n saveFallbackHtml(id, element)\n }\n },\n [id],\n )\n const markerProps = {\n ref: markerRef,\n [hydrateIdAttribute]: id,\n [hydrateWhenAttribute]: neverType,\n }\n const fallback = (() => {\n const html = getFallbackHtml(id)\n return html ? (\n <div\n style={{ display: 'contents' }}\n dangerouslySetInnerHTML={{ __html: html }}\n />\n ) : (\n (props.fallback ?? null)\n )\n })()\n\n return (\n <div {...markerProps}>\n <React.Suspense fallback={fallback}>\n <NeverGate>{props.children}</NeverGate>\n </React.Suspense>\n </div>\n )\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function never(): ReactHydrationStrategy<'never', false> {\n return /* @__PURE__ */ withHydrationRenderer(coreNever(), NeverHydrate)\n}\n"],"mappings":";;;;;;;;;AAwBA,IAAM,YAAY;AAClB,IAAM,eAAe,IAAI,cAAoB,GAAG;AAEhD,SAAS,UAAU,OAAsC;AACvD,KAEE,YACA,OAAO,WAAW,YAElB,QAAO,MAAM;AAGf,KAAI,CAAC,SACH,OAAM;AAGR,UAAS,aAAa;AAEtB,QAAO,MAAM;;AAGf,SAAgB,aAAa,OAAwC;CACnE,MAAM,gBAAgB;CACtB,MAAM,WAAW,aAAa;CAC9B,MAAM,UAAU,MAAM,OAAO;CAC7B,MAAM,KAAK,cAAc,IAAI,GAAG,cAAc,IAAI,YAAY;CAC9D,MAAM,8BAA8B,MAAM,OACxC,KAAA,EACD;AACD,6BAA4B,aAEzB,YAAY,OAAO,WAAW,gBAAgB,CAAC;CAYlD,MAAM,cAAc;EAClB,KAZgB,MAAM,aACrB,YAAmC;AAClC,OAAI,CAAC,QAAS;AACd,OAAI,CAAC,4BAA4B,QAC/B,SAAQ,iBAAiB;OAEzB,kBAAiB,IAAI,QAAQ;KAGjC,CAAC,GAAG,CACL;GAGE,qBAAqB;GACrB,uBAAuB;EACzB;CACD,MAAM,kBAAkB;EACtB,MAAM,OAAO,gBAAgB,GAAG;AAChC,SAAO,OACL,oBAAC,OAAD;GACE,OAAO,EAAE,SAAS,YAAY;GAC9B,yBAAyB,EAAE,QAAQ,MAAM;GACzC,CAAA,GAED,MAAM,YAAY;KAEnB;AAEJ,QACE,oBAAC,OAAD;EAAK,GAAI;YACP,oBAAC,MAAM,UAAP;GAA0B;aACxB,oBAAC,WAAD,EAAA,UAAY,MAAM,UAAqB,CAAA;GACxB,CAAA;EACb,CAAA;;;AAKV,SAAgB,UAAgD;AAC9D,QAAuB,sCAAsB,OAAW,EAAE,aAAa"}
import { HydrationPrefetchStrategy, VisibleHydrationOptions } from '@tanstack/start-client-core/hydration';
import { HydrateProps, ReactHydrationStrategy } from '../Hydrate.js';
import * as React from 'react';
export declare function VisibleHydrate(this: ReactHydrationStrategy, props: HydrateProps): React.JSX.Element;
export declare function visible(options?: VisibleHydrationOptions): ReactHydrationStrategy<'visible', true> & HydrationPrefetchStrategy<'visible'>;
"use client";
import { reactUse } from "@tanstack/react-router";
import { jsx } from "react/jsx-runtime";
import * as React from "react";
import { isServer } from "@tanstack/router-core/isServer";
//#region src/hydration/visible.tsx
/* @__NO_SIDE_EFFECTS__ */
function HydrationBoundary(props) {
const { g, o } = props;
if (!g.r) {
if (!reactUse) throw g.p;
reactUse(g.p);
}
React.useEffect(() => {
o?.();
}, [o]);
return props.children;
}
/* @__NO_SIDE_EFFECTS__ */
function VisibleHydrate(props) {
const strategy = this;
const prefetchStrategy = props.prefetch;
const preload = props.p;
const markerRef = React.useRef(null);
const [gate] = React.useState(() => {
let resolvePromise;
const nextGate = {
p: new Promise((resolve) => {
resolvePromise = resolve;
}),
r: false,
s: () => {
nextGate.r = true;
resolvePromise();
}
};
if (isServer ?? typeof window === "undefined") nextGate.s();
return nextGate;
});
React.useEffect(() => {
if (!preload || typeof prefetchStrategy === "function") return;
return prefetchStrategy?._s?.({
element: markerRef.current,
prefetch: preload
});
}, [prefetchStrategy, preload]);
React.useEffect(() => {
if (gate.r) return;
return strategy._s?.({
element: markerRef.current,
gate
});
}, [gate, strategy]);
return /* @__PURE__ */ jsx("div", {
ref: markerRef,
children: /* @__PURE__ */ jsx(React.Suspense, {
fallback: props.fallback,
children: /* @__PURE__ */ jsx(HydrationBoundary, {
g: gate,
o: props.onHydrated,
children: props.children
})
})
});
}
/* @__NO_SIDE_EFFECTS__ */
function visible(options) {
const rootMargin = options?.rootMargin ?? "600px";
const threshold = options?.threshold ?? 0;
return {
_s: ({ element, gate, prefetch }) => {
const callback = prefetch || gate.s;
if (!element) {
callback();
return;
}
const observer = new IntersectionObserver((entries) => {
if (!entries[0].isIntersecting) return;
observer.disconnect();
callback();
}, {
rootMargin,
threshold
});
observer.observe(element);
return () => observer.disconnect();
},
_h: VisibleHydrate
};
}
//#endregion
export { visible };
//# sourceMappingURL=visible.js.map
{"version":3,"file":"visible.js","names":[],"sources":["../../../src/hydration/visible.tsx"],"sourcesContent":["'use client'\n\nimport * as React from 'react'\n\nimport { reactUse } from '@tanstack/react-router'\nimport { isServer } from '@tanstack/router-core/isServer'\nimport type {\n HydrationPrefetchStrategy,\n VisibleHydrationOptions,\n} from '@tanstack/start-client-core/hydration'\nimport type {\n HydrateProps,\n InternalHydrateProps,\n ReactHydrationStrategy,\n} from '../Hydrate'\n\ntype VisibleGate = {\n p: Promise<void>\n r: boolean\n s: () => void\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nfunction HydrationBoundary(props: {\n g: VisibleGate\n o?: () => void\n children?: React.ReactNode\n}) {\n const { g, o } = props\n\n if (!g.r) {\n if (!reactUse) {\n throw g.p\n }\n\n reactUse(g.p)\n }\n\n React.useEffect(() => {\n o?.()\n }, [o])\n\n return props.children as React.JSX.Element\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function VisibleHydrate(\n this: ReactHydrationStrategy,\n props: HydrateProps,\n): React.JSX.Element {\n const strategy = this as ReactHydrationStrategy<'visible', true>\n const prefetchStrategy = props.prefetch\n const preload = (props as InternalHydrateProps).p\n const markerRef = React.useRef<HTMLDivElement | null>(null)\n const [gate] = React.useState<VisibleGate>(() => {\n let resolvePromise!: () => void\n const nextGate: VisibleGate = {\n p: new Promise<void>((resolve) => {\n resolvePromise = resolve\n }),\n r: false,\n s: () => {\n nextGate.r = true\n resolvePromise()\n },\n }\n if (\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n isServer ??\n typeof window === 'undefined'\n ) {\n nextGate.s()\n }\n\n return nextGate\n })\n\n React.useEffect(() => {\n if (!preload || typeof prefetchStrategy === 'function') {\n return\n }\n\n return prefetchStrategy?._s?.({\n element: markerRef.current,\n prefetch: preload,\n })\n }, [prefetchStrategy, preload])\n\n React.useEffect(() => {\n if (gate.r) return\n\n return strategy._s?.({\n element: markerRef.current,\n gate: gate as never,\n })\n }, [gate, strategy])\n\n return (\n <div ref={markerRef}>\n <React.Suspense fallback={props.fallback}>\n <HydrationBoundary g={gate} o={props.onHydrated}>\n {props.children}\n </HydrationBoundary>\n </React.Suspense>\n </div>\n )\n}\n\n/* @__NO_SIDE_EFFECTS__ */\nexport function visible(\n options?: VisibleHydrationOptions,\n): ReactHydrationStrategy<'visible', true> &\n HydrationPrefetchStrategy<'visible'> {\n const rootMargin = options?.rootMargin ?? '600px'\n const threshold = options?.threshold ?? 0\n\n return {\n _s: ({ element, gate, prefetch }) => {\n const callback = prefetch || (gate as never as VisibleGate).s\n\n if (!element) {\n callback()\n return\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (!entries[0]!.isIntersecting) return\n observer.disconnect()\n callback()\n },\n { rootMargin, threshold },\n )\n observer.observe(element)\n return () => observer.disconnect()\n },\n _h: VisibleHydrate,\n }\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,kBAAkB,OAIxB;CACD,MAAM,EAAE,GAAG,MAAM;AAEjB,KAAI,CAAC,EAAE,GAAG;AACR,MAAI,CAAC,SACH,OAAM,EAAE;AAGV,WAAS,EAAE,EAAE;;AAGf,OAAM,gBAAgB;AACpB,OAAK;IACJ,CAAC,EAAE,CAAC;AAEP,QAAO,MAAM;;;AAIf,SAAgB,eAEd,OACmB;CACnB,MAAM,WAAW;CACjB,MAAM,mBAAmB,MAAM;CAC/B,MAAM,UAAW,MAA+B;CAChD,MAAM,YAAY,MAAM,OAA8B,KAAK;CAC3D,MAAM,CAAC,QAAQ,MAAM,eAA4B;EAC/C,IAAI;EACJ,MAAM,WAAwB;GAC5B,GAAG,IAAI,SAAe,YAAY;AAChC,qBAAiB;KACjB;GACF,GAAG;GACH,SAAS;AACP,aAAS,IAAI;AACb,oBAAgB;;GAEnB;AACD,MAEE,YACA,OAAO,WAAW,YAElB,UAAS,GAAG;AAGd,SAAO;GACP;AAEF,OAAM,gBAAgB;AACpB,MAAI,CAAC,WAAW,OAAO,qBAAqB,WAC1C;AAGF,SAAO,kBAAkB,KAAK;GAC5B,SAAS,UAAU;GACnB,UAAU;GACX,CAAC;IACD,CAAC,kBAAkB,QAAQ,CAAC;AAE/B,OAAM,gBAAgB;AACpB,MAAI,KAAK,EAAG;AAEZ,SAAO,SAAS,KAAK;GACnB,SAAS,UAAU;GACb;GACP,CAAC;IACD,CAAC,MAAM,SAAS,CAAC;AAEpB,QACE,oBAAC,OAAD;EAAK,KAAK;YACR,oBAAC,MAAM,UAAP;GAAgB,UAAU,MAAM;aAC9B,oBAAC,mBAAD;IAAmB,GAAG;IAAM,GAAG,MAAM;cAClC,MAAM;IACW,CAAA;GACL,CAAA;EACb,CAAA;;;AAKV,SAAgB,QACd,SAEqC;CACrC,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,YAAY,SAAS,aAAa;AAExC,QAAO;EACL,KAAK,EAAE,SAAS,MAAM,eAAe;GACnC,MAAM,WAAW,YAAa,KAA8B;AAE5D,OAAI,CAAC,SAAS;AACZ,cAAU;AACV;;GAGF,MAAM,WAAW,IAAI,sBAClB,YAAY;AACX,QAAI,CAAC,QAAQ,GAAI,eAAgB;AACjC,aAAS,YAAY;AACrB,cAAU;MAEZ;IAAE;IAAY;IAAW,CAC1B;AACD,YAAS,QAAQ,QAAQ;AACzB,gBAAa,SAAS,YAAY;;EAEpC,IAAI;EACL"}
'use client'
import * as React from 'react'
import { reactUse, useHydrated, useLayoutEffect } from '@tanstack/react-router'
import { isServer } from '@tanstack/router-core/isServer'
import {
hydrateIdAttribute,
hydrateWhenAttribute,
} from '@tanstack/start-client-core/hydration/constants'
import {
createResolvedGate,
getFallbackHtml,
getOrCreateGate,
onGateResolve,
releaseGate,
runHydrationStrategyCleanup,
saveFallbackHtml,
waitForHydrationPrefetchStrategy,
} from '@tanstack/start-client-core/hydration/runtime'
import { listenForDelegatedHydrationIntent } from '@tanstack/start-client-core/hydration'
import type {
HydrationRuntimeContext,
HydrationStrategy,
HydrationWhen,
} from '@tanstack/start-client-core/hydration'
import type { HydrationGateRecord } from '@tanstack/start-client-core/hydration/runtime'
import type { HydrateProps, InternalHydrateProps } from './Hydrate'
type Gate = HydrationGateRecord & { promise: Promise<void> }
type PrefetchController = {
abortController: AbortController
hydrationRequested: boolean
hydrationListeners: Set<() => void>
hydrationResolvePending: boolean
started: boolean
promise?: Promise<void>
cleanup?: () => void
}
const dynamicType = 'dynamic'
const dynamicHydrateStrategy = {
_t: dynamicType,
_d: () => true,
} satisfies HydrationStrategy<typeof dynamicType, false>
function shouldDeferHydration(strategy: HydrationStrategy) {
return strategy._d ? strategy._d() : strategy._t !== 'load'
}
function useLatest<T>(value: T) {
const ref = React.useRef(value)
ref.current = value
return ref
}
function useHydrationGate(props: InternalHydrateProps) {
const hydrated = useHydrated()
const reactId = React.useId()
const id = props.h ? `${props.h}${reactId}` : reactId
const when = props.when
const isDynamicHydrate = typeof when === 'function'
const dynamicHydrateStrategyRef = React.useRef<HydrationStrategy | undefined>(
undefined,
)
if (isDynamicHydrate) {
dynamicHydrateStrategyRef.current ??=
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(isServer ?? typeof window === 'undefined')
? dynamicHydrateStrategy
: when()
}
const hydrateStrategy = isDynamicHydrate
? dynamicHydrateStrategyRef.current!
: when
const markerHydrateType: HydrationWhen = isDynamicHydrate
? dynamicType
: hydrateStrategy._t!
const [prefetchError, setPrefetchError] = React.useState<unknown>()
const latestRef = useLatest({
prefetch: props.prefetch,
preload: props.p,
})
const gateRef = React.useRef<HydrationGateRecord | undefined>(undefined)
const markerElementRef = React.useRef<HTMLDivElement | null>(null)
const shouldPreserveServerHTMLRef = React.useRef<boolean | undefined>(
undefined,
)
const shouldDeferInitialHydrationRef = React.useRef<boolean | undefined>(
undefined,
)
const didPrefetchRef = React.useRef(false)
const prefetchControllerRef = React.useRef<PrefetchController | undefined>(
undefined,
)
prefetchControllerRef.current ??= {
abortController: new AbortController(),
hydrationRequested: false,
hydrationListeners: new Set<() => void>(),
hydrationResolvePending: false,
started: false,
}
shouldPreserveServerHTMLRef.current ??=
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(isServer ?? typeof window === 'undefined') || !hydrated
shouldDeferInitialHydrationRef.current ??=
!hydrated && shouldDeferHydration(hydrateStrategy)
if (!gateRef.current) {
gateRef.current =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(isServer ?? typeof window === 'undefined')
? createResolvedGate(id, hydrateStrategy._t!)
: getOrCreateGate(id, hydrateStrategy._t!)
}
gateRef.current.when = hydrateStrategy._t!
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
!(isServer ?? typeof window === 'undefined') &&
hydrateStrategy._t !== 'never' &&
(!shouldDeferInitialHydrationRef.current ||
!shouldDeferHydration(hydrateStrategy))
) {
gateRef.current.resolve()
}
const markerRef = React.useCallback(
(element: HTMLDivElement | null) => {
markerElementRef.current = element
if (element) {
if (
hydrateStrategy._t === 'never' &&
!shouldPreserveServerHTMLRef.current
) {
element.replaceChildren()
}
saveFallbackHtml(id, element)
}
},
[hydrateStrategy._t, id],
)
React.useEffect(() => {
const gate = gateRef.current!
return () => {
const controller = prefetchControllerRef.current
controller?.abortController.abort()
controller?.cleanup?.()
controller?.hydrationListeners.clear()
releaseGate(gate)
}
}, [])
React.useEffect(() => {
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(isServer ?? typeof window === 'undefined') ||
!latestRef.current.prefetch
) {
return
}
const controller = prefetchControllerRef.current!
if (controller.started) return
controller.started = true
const onHydrate = (listener: () => void) => {
if (controller.hydrationRequested) {
listener()
return () => {}
}
controller.hydrationListeners.add(listener)
return () => {
controller.hydrationListeners.delete(listener)
}
}
const preload = () => latestRef.current.preload?.() ?? Promise.resolve()
const prefetchInput = latestRef.current.prefetch
if (typeof prefetchInput === 'function') {
const promise = Promise.resolve()
.then(() =>
prefetchInput({
element: markerElementRef.current,
signal: controller.abortController.signal,
preload,
waitFor: (strategy) =>
waitForHydrationPrefetchStrategy(strategy, {
element: markerElementRef.current,
signal: controller.abortController.signal,
onHydrate,
}),
}),
)
.then(() => undefined)
controller.promise = promise
promise.catch((error) => {
if (!controller.abortController.signal.aborted) {
setPrefetchError(error)
}
})
return
}
if (!latestRef.current.preload) return
const prefetch = () => {
if (didPrefetchRef.current) return
didPrefetchRef.current = true
void preload()
}
controller.cleanup = runHydrationStrategyCleanup(
prefetchInput._s?.({
element: markerElementRef.current,
prefetch,
}),
)
}, [hydrateStrategy, latestRef])
useLayoutEffect(() => {
const gate = gateRef.current!
if (
!shouldDeferInitialHydrationRef.current ||
hydrateStrategy._t === 'never'
) {
return
}
if (gate.resolved) {
return
}
const cleanups: Array<() => void> = []
let removeResolveListener = () => {}
let disposed = false
const resolveGate = gate.resolve
const cleanup = () => {
if (disposed) return
disposed = true
if (gate.resolve === requestHydration) {
gate.resolve = resolveGate
}
removeResolveListener()
cleanups.forEach((fn) => fn())
}
const addCleanup = (fn: void | (() => void)) => {
if (!fn) return
if (disposed || gate.resolved) {
fn()
return
}
cleanups.push(fn)
}
const requestHydration = () => {
const controller = prefetchControllerRef.current!
if (!controller.hydrationRequested) {
controller.hydrationRequested = true
controller.hydrationListeners.forEach((listener) => listener())
controller.hydrationListeners.clear()
}
if (!controller.promise) {
resolveGate()
return
}
if (controller.hydrationResolvePending) return
controller.hydrationResolvePending = true
controller.promise.then(
() => resolveGate(),
(error) => {
if (!controller.abortController.signal.aborted) {
setPrefetchError(error)
}
},
)
}
gate.resolve = requestHydration
removeResolveListener = onGateResolve(gate, cleanup)
const context: HydrationRuntimeContext = {
element: markerElementRef.current,
gate,
}
addCleanup(runHydrationStrategyCleanup(hydrateStrategy._s?.(context)))
if (hydrateStrategy._t !== 'interaction') {
addCleanup(
runHydrationStrategyCleanup(
markerElementRef.current
? listenForDelegatedHydrationIntent(
markerElementRef.current,
context,
)
: undefined,
),
)
}
return cleanup
}, [hydrateStrategy, latestRef])
return {
gate: gateRef.current,
markerRef,
markerElementRef,
hydrateStrategy,
markerHydrateType,
prefetchError,
shouldPreserveServerHTML: shouldPreserveServerHTMLRef.current,
}
}
function HydrationGate(props: { gate: Gate; children: React.ReactNode }) {
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
isServer ??
typeof window === 'undefined'
) {
return props.children as React.JSX.Element
}
if (props.gate.resolved) {
return props.children as React.JSX.Element
}
if (!reactUse) {
throw props.gate.promise
}
reactUse(props.gate.promise)
return props.children as React.JSX.Element
}
function HydratedBoundary(props: {
id: string
onHydrated?: () => void
onStrategyHydrated?: (id: string) => void
children: React.ReactNode
}) {
const { id, onHydrated, onStrategyHydrated } = props
const didHydrateRef = React.useRef(false)
React.useEffect(() => {
if (didHydrateRef.current) return
didHydrateRef.current = true
onHydrated?.()
onStrategyHydrated?.(id)
}, [id, onHydrated, onStrategyHydrated])
return props.children as React.JSX.Element
}
export function GenericHydrate(props: HydrateProps): React.JSX.Element {
const internalProps = props as InternalHydrateProps
const {
gate,
hydrateStrategy,
markerHydrateType,
markerElementRef,
markerRef,
prefetchError,
shouldPreserveServerHTML,
} = useHydrationGate(internalProps)
if (prefetchError) throw prefetchError
const fallback = shouldPreserveServerHTML
? (() => {
const html = getFallbackHtml(gate.id)
return html ? (
<div
style={{ display: 'contents' }}
dangerouslySetInnerHTML={{ __html: html }}
/>
) : null
})()
: (props.fallback ?? null)
const markerAttributes =
markerHydrateType === dynamicType ? undefined : hydrateStrategy._a?.()
const hydrateType = hydrateStrategy._t!
if (hydrateType === 'never' && !shouldPreserveServerHTML) {
return (
<div
ref={markerRef}
{...{
[hydrateIdAttribute]: gate.id,
[hydrateWhenAttribute]: markerHydrateType,
...markerAttributes,
}}
>
{props.fallback ?? null}
</div>
)
}
return (
<div
ref={markerRef}
{...{
[hydrateIdAttribute]: gate.id,
[hydrateWhenAttribute]: markerHydrateType,
...markerAttributes,
}}
>
<React.Suspense fallback={fallback}>
<HydrationGate gate={gate}>
<HydratedBoundary
id={gate.id}
onHydrated={props.onHydrated}
onStrategyHydrated={(id) => {
markerElementRef.current?.removeAttribute(hydrateWhenAttribute)
hydrateStrategy._o?.(id)
}}
>
{props.children}
</HydratedBoundary>
</HydrationGate>
</React.Suspense>
</div>
)
}
'use client'
import * as React from 'react'
import { isServer } from '@tanstack/router-core/isServer'
import type {
HydrationStrategy as CoreHydrationStrategy,
HydrationPrefetchFunction,
HydrationPrefetchStrategy,
HydrationWhen,
} from '@tanstack/start-client-core/hydration'
export type {
HydrationInteractionEvent,
HydrationInteractionEvents,
HydrationPrefetchContext,
HydrationPrefetchFunction,
HydrationPrefetchStrategy,
HydrationPrefetchWaitReason,
HydrationWhen,
} from '@tanstack/start-client-core/hydration'
export type ReactHydrationStrategy<
TWhen extends HydrationWhen = HydrationWhen,
TCanPrefetch extends boolean = boolean,
> = CoreHydrationStrategy<TWhen, TCanPrefetch> & {
_h: (this: ReactHydrationStrategy, props: HydrateProps) => React.JSX.Element
}
export type HydrationStrategy<
TWhen extends HydrationWhen = HydrationWhen,
TCanPrefetch extends boolean = boolean,
> = ReactHydrationStrategy<TWhen, TCanPrefetch>
export type HydrateWhen =
| ReactHydrationStrategy
| (() => ReactHydrationStrategy)
type HydrateCommonOptions = {
when: HydrateWhen
fallback?: React.ReactNode
onHydrated?: () => void
}
export type HydrateOptions =
| (HydrateCommonOptions & {
prefetch?: never
split?: boolean
})
| (HydrateCommonOptions & {
prefetch: HydrationPrefetchStrategy
split?: true
})
| (HydrateCommonOptions & {
prefetch: HydrationPrefetchFunction
split?: boolean
})
export type HydrateProps = HydrateOptions & {
children: React.ReactNode
}
export type InternalHydrateProps = HydrateProps & {
h?: string
p?: () => Promise<void>
}
const dynamicType = 'dynamic'
const hydrateIdAttribute = 'data-ts-hydrate-id'
const hydrateWhenAttribute = 'data-ts-hydrate-when'
/* @__NO_SIDE_EFFECTS__ */
function ServerDynamicHydrate(props: HydrateProps): React.JSX.Element {
const internalProps = props as InternalHydrateProps
const reactId = React.useId()
const id = internalProps.h ? `${internalProps.h}${reactId}` : reactId
return (
<div
{...{
[hydrateIdAttribute]: id,
[hydrateWhenAttribute]: dynamicType,
}}
>
<React.Suspense fallback={props.fallback ?? null}>
{props.children}
</React.Suspense>
</div>
)
}
/* @__NO_SIDE_EFFECTS__ */
export function Hydrate(props: HydrateProps): React.JSX.Element {
if (typeof props.when === 'function') {
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
isServer ??
typeof window === 'undefined'
) {
return <ServerDynamicHydrate {...props} />
}
return props.when()._h(props)
}
return props.when._h(props)
}
'use client'
export { condition, interaction, media } from './hydration/generic'
export { idle } from './hydration/idle'
export { load } from './hydration/load'
export { never } from './hydration/never'
export { visible } from './hydration/visible'
export type {
HydrationCondition,
HydrationInteractionEvent,
HydrationInteractionEvents,
IdleHydrationOptions,
HydrationPrefetchContext,
HydrationPrefetchFunction,
HydrationPrefetchWhen,
HydrationPrefetchStrategy,
HydrationPrefetchWaitReason,
HydrationStrategyTypes,
HydrationWhen,
VisibleHydrationOptions,
} from '@tanstack/start-client-core/hydration'
export type { HydrationStrategy, ReactHydrationStrategy } from './Hydrate'
'use client'
import {
condition as coreCondition,
interaction as coreInteraction,
media as coreMedia,
withHydrationRenderer,
} from '@tanstack/start-client-core/hydration'
import { GenericHydrate } from '../GenericHydrate'
import type {
HydrationCondition,
HydrationInteractionEvents,
HydrationPrefetchStrategy,
} from '@tanstack/start-client-core/hydration'
import type { ReactHydrationStrategy } from '../Hydrate'
/* @__NO_SIDE_EFFECTS__ */
export function media(
query: string,
): ReactHydrationStrategy<'media', true> & HydrationPrefetchStrategy<'media'> {
return /* @__PURE__ */ withHydrationRenderer(coreMedia(query), GenericHydrate)
}
/* @__NO_SIDE_EFFECTS__ */
export function condition(
condition: HydrationCondition,
): ReactHydrationStrategy<'condition', false> {
return /* @__PURE__ */ withHydrationRenderer(
coreCondition(condition),
GenericHydrate,
)
}
/* @__NO_SIDE_EFFECTS__ */
export function interaction(options?: {
events?: HydrationInteractionEvents
}): ReactHydrationStrategy<'interaction', true> &
HydrationPrefetchStrategy<'interaction'> {
return /* @__PURE__ */ withHydrationRenderer(
coreInteraction(options),
GenericHydrate,
)
}
'use client'
import {
idle as coreIdle,
withHydrationRenderer,
} from '@tanstack/start-client-core/hydration'
import { GenericHydrate } from '../GenericHydrate'
import type {
HydrationPrefetchStrategy,
IdleHydrationOptions,
} from '@tanstack/start-client-core/hydration'
import type { ReactHydrationStrategy } from '../Hydrate'
/* @__NO_SIDE_EFFECTS__ */
export function idle(
options: IdleHydrationOptions = {},
): ReactHydrationStrategy<'idle', true> & HydrationPrefetchStrategy<'idle'> {
return /* @__PURE__ */ withHydrationRenderer(
coreIdle(options),
GenericHydrate,
)
}
'use client'
import * as React from 'react'
import {
load as coreLoad,
withHydrationRenderer,
} from '@tanstack/start-client-core/hydration'
import type { HydrationPrefetchStrategy } from '@tanstack/start-client-core/hydration'
import type { HydrateProps, ReactHydrationStrategy } from '../Hydrate'
function HydratedBoundary(props: {
onHydrated?: () => void
children: React.ReactNode
}) {
const { onHydrated, children } = props
const didHydrateRef = React.useRef(false)
React.useEffect(() => {
if (didHydrateRef.current) return
didHydrateRef.current = true
onHydrated?.()
}, [onHydrated])
return children as React.JSX.Element
}
export function LoadHydrate(props: HydrateProps): React.JSX.Element {
return (
<div>
<React.Suspense fallback={props.fallback ?? null}>
<HydratedBoundary onHydrated={props.onHydrated}>
{props.children}
</HydratedBoundary>
</React.Suspense>
</div>
)
}
const loadStrategy = /* @__PURE__ */ withHydrationRenderer(
coreLoad(),
LoadHydrate,
) as ReactHydrationStrategy<'load', true> & HydrationPrefetchStrategy<'load'>
/* @__NO_SIDE_EFFECTS__ */
export function load(): ReactHydrationStrategy<'load', true> &
HydrationPrefetchStrategy<'load'> {
return loadStrategy
}
'use client'
import * as React from 'react'
import { reactUse, useHydrated } from '@tanstack/react-router'
import { isServer } from '@tanstack/router-core/isServer'
import {
never as coreNever,
withHydrationRenderer,
} from '@tanstack/start-client-core/hydration'
import {
hydrateIdAttribute,
hydrateWhenAttribute,
} from '@tanstack/start-client-core/hydration/constants'
import {
getFallbackHtml,
saveFallbackHtml,
} from '@tanstack/start-client-core/hydration/runtime'
import type {
HydrateProps,
InternalHydrateProps,
ReactHydrationStrategy,
} from '../Hydrate'
const neverType = 'never'
const neverPromise = new Promise<void>(() => {})
function NeverGate(props: { children: React.ReactNode }) {
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
isServer ??
typeof window === 'undefined'
) {
return props.children as React.JSX.Element
}
if (!reactUse) {
throw neverPromise
}
reactUse(neverPromise)
return props.children as React.JSX.Element
}
export function NeverHydrate(props: HydrateProps): React.JSX.Element {
const internalProps = props as InternalHydrateProps
const hydrated = useHydrated()
const reactId = React.useId()
const id = internalProps.h ? `${internalProps.h}${reactId}` : reactId
const shouldPreserveServerHTMLRef = React.useRef<boolean | undefined>(
undefined,
)
shouldPreserveServerHTMLRef.current ??=
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
(isServer ?? typeof window === 'undefined') || !hydrated
const markerRef = React.useCallback(
(element: HTMLDivElement | null) => {
if (!element) return
if (!shouldPreserveServerHTMLRef.current) {
element.replaceChildren()
} else {
saveFallbackHtml(id, element)
}
},
[id],
)
const markerProps = {
ref: markerRef,
[hydrateIdAttribute]: id,
[hydrateWhenAttribute]: neverType,
}
const fallback = (() => {
const html = getFallbackHtml(id)
return html ? (
<div
style={{ display: 'contents' }}
dangerouslySetInnerHTML={{ __html: html }}
/>
) : (
(props.fallback ?? null)
)
})()
return (
<div {...markerProps}>
<React.Suspense fallback={fallback}>
<NeverGate>{props.children}</NeverGate>
</React.Suspense>
</div>
)
}
/* @__NO_SIDE_EFFECTS__ */
export function never(): ReactHydrationStrategy<'never', false> {
return /* @__PURE__ */ withHydrationRenderer(coreNever(), NeverHydrate)
}
'use client'
import * as React from 'react'
import { reactUse } from '@tanstack/react-router'
import { isServer } from '@tanstack/router-core/isServer'
import type {
HydrationPrefetchStrategy,
VisibleHydrationOptions,
} from '@tanstack/start-client-core/hydration'
import type {
HydrateProps,
InternalHydrateProps,
ReactHydrationStrategy,
} from '../Hydrate'
type VisibleGate = {
p: Promise<void>
r: boolean
s: () => void
}
/* @__NO_SIDE_EFFECTS__ */
function HydrationBoundary(props: {
g: VisibleGate
o?: () => void
children?: React.ReactNode
}) {
const { g, o } = props
if (!g.r) {
if (!reactUse) {
throw g.p
}
reactUse(g.p)
}
React.useEffect(() => {
o?.()
}, [o])
return props.children as React.JSX.Element
}
/* @__NO_SIDE_EFFECTS__ */
export function VisibleHydrate(
this: ReactHydrationStrategy,
props: HydrateProps,
): React.JSX.Element {
const strategy = this as ReactHydrationStrategy<'visible', true>
const prefetchStrategy = props.prefetch
const preload = (props as InternalHydrateProps).p
const markerRef = React.useRef<HTMLDivElement | null>(null)
const [gate] = React.useState<VisibleGate>(() => {
let resolvePromise!: () => void
const nextGate: VisibleGate = {
p: new Promise<void>((resolve) => {
resolvePromise = resolve
}),
r: false,
s: () => {
nextGate.r = true
resolvePromise()
},
}
if (
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
isServer ??
typeof window === 'undefined'
) {
nextGate.s()
}
return nextGate
})
React.useEffect(() => {
if (!preload || typeof prefetchStrategy === 'function') {
return
}
return prefetchStrategy?._s?.({
element: markerRef.current,
prefetch: preload,
})
}, [prefetchStrategy, preload])
React.useEffect(() => {
if (gate.r) return
return strategy._s?.({
element: markerRef.current,
gate: gate as never,
})
}, [gate, strategy])
return (
<div ref={markerRef}>
<React.Suspense fallback={props.fallback}>
<HydrationBoundary g={gate} o={props.onHydrated}>
{props.children}
</HydrationBoundary>
</React.Suspense>
</div>
)
}
/* @__NO_SIDE_EFFECTS__ */
export function visible(
options?: VisibleHydrationOptions,
): ReactHydrationStrategy<'visible', true> &
HydrationPrefetchStrategy<'visible'> {
const rootMargin = options?.rootMargin ?? '600px'
const threshold = options?.threshold ?? 0
return {
_s: ({ element, gate, prefetch }) => {
const callback = prefetch || (gate as never as VisibleGate).s
if (!element) {
callback()
return
}
const observer = new IntersectionObserver(
(entries) => {
if (!entries[0]!.isIntersecting) return
observer.disconnect()
callback()
},
{ rootMargin, threshold },
)
observer.observe(element)
return () => observer.disconnect()
},
_h: VisibleHydrate,
}
}
import { expectTypeOf, test } from 'vitest'
import { visible } from '../hydration'
import { Hydrate } from '../Hydrate'
import type {
HydrateOptions,
HydrateProps,
HydrationPrefetchFunction,
HydrationPrefetchStrategy,
HydrationStrategy,
} from '../Hydrate'
import type { HydrationStrategy as CoreHydrationStrategy } from '@tanstack/start-client-core/hydration'
import type { ReactNode } from 'react'
type CommonHydrateProps = {
fallback?: ReactNode
onHydrated?: () => void
children: ReactNode
}
type SplitHydrateProps = CommonHydrateProps & {
when: HydrationStrategy | (() => HydrationStrategy)
prefetch?: never
split?: boolean
}
type PrefetchHydrateProps = CommonHydrateProps & {
when: HydrationStrategy | (() => HydrationStrategy)
prefetch: HydrationPrefetchStrategy
split?: true
}
type FunctionPrefetchHydrateProps = CommonHydrateProps & {
when: HydrationStrategy | (() => HydrationStrategy)
prefetch: HydrationPrefetchFunction
split?: boolean
}
test('Hydrate component accepts the public HydrateProps type', () => {
expectTypeOf(Hydrate).toBeFunction()
expectTypeOf(Hydrate).parameter(0).branded.toEqualTypeOf<HydrateProps>()
})
test('HydrateOptions supports reusable spread props', () => {
const belowFoldProps = {
when: () => visible({ rootMargin: '800px' }),
} satisfies HydrateOptions
expectTypeOf(belowFoldProps).toMatchTypeOf<HydrateOptions>()
const withFunctionPrefetch = {
when: visible(),
split: false,
prefetch: (ctx) => {
expectTypeOf(ctx.element).toEqualTypeOf<Element | null>()
expectTypeOf(ctx.signal).toEqualTypeOf<AbortSignal>()
expectTypeOf(ctx.preload).returns.toEqualTypeOf<Promise<void>>()
expectTypeOf(ctx.waitFor).returns.toEqualTypeOf<
Promise<'prefetch' | 'hydrate' | 'abort'>
>()
},
} satisfies HydrateOptions
expectTypeOf(withFunctionPrefetch).toMatchTypeOf<HydrateOptions>()
})
test('Hydrate props are exact for strategy and prefetch forms', () => {
expectTypeOf<
Extract<HydrateProps, { prefetch?: never }>
>().branded.toEqualTypeOf<SplitHydrateProps>()
expectTypeOf<
Extract<HydrateProps, { prefetch: HydrationPrefetchStrategy }>
>().branded.toEqualTypeOf<PrefetchHydrateProps>()
expectTypeOf<
Extract<HydrateProps, { prefetch: HydrationPrefetchFunction }>
>().branded.toEqualTypeOf<FunctionPrefetchHydrateProps>()
})
test('Hydrate requires a strategy', () => {
expectTypeOf<{
when: HydrationStrategy
children: ReactNode
}>().toMatchTypeOf<HydrateProps>()
expectTypeOf<{
when: () => HydrationStrategy
children: ReactNode
}>().toMatchTypeOf<HydrateProps>()
expectTypeOf<{
children: ReactNode
}>().not.toMatchTypeOf<HydrateProps>()
expectTypeOf<{
when: () => true
children: ReactNode
}>().not.toMatchTypeOf<HydrateProps>()
expectTypeOf<{
when: false
children: ReactNode
}>().not.toMatchTypeOf<HydrateProps>()
})
test('Hydrate requires a framework-renderable strategy', () => {
expectTypeOf<CoreHydrationStrategy>().not.toMatchTypeOf<HydrationStrategy>()
expectTypeOf<ReturnType<typeof visible>>().toMatchTypeOf<HydrationStrategy>()
expectTypeOf<{
when: CoreHydrationStrategy
children: ReactNode
}>().not.toMatchTypeOf<HydrateProps>()
})
test('Hydrate enforces prefetch only with split boundaries', () => {
expectTypeOf<{
when: HydrationStrategy
prefetch: HydrationPrefetchStrategy
children: ReactNode
}>().toMatchTypeOf<HydrateProps>()
expectTypeOf<{
when: HydrationStrategy
prefetch: HydrationPrefetchStrategy
split: true
children: ReactNode
}>().toMatchTypeOf<HydrateProps>()
expectTypeOf<{
when: HydrationStrategy
prefetch: HydrationPrefetchStrategy
split: false
children: ReactNode
}>().not.toMatchTypeOf<HydrateProps>()
expectTypeOf<{
when: HydrationStrategy
prefetch: HydrationPrefetchFunction
split: false
children: ReactNode
}>().toMatchTypeOf<HydrateProps>()
expectTypeOf<{
when: HydrationStrategy
prefetch: HydrationPrefetchFunction
children: ReactNode
}>().toMatchTypeOf<HydrateProps>()
})
import * as React from 'react'
import { renderToString } from 'react-dom/server'
import { hydrateRoot } from 'react-dom/client'
import {
act,
cleanup,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { hydrateIdAttribute } from '@tanstack/start-client-core/hydration/constants'
import { Hydrate } from '../Hydrate'
import { condition, idle, interaction, load, never } from '../hydration'
import type { HydrateProps, HydrationPrefetchStrategy } from '../Hydrate'
const InternalHydrate = Hydrate as React.ComponentType<
HydrateProps & { p?: () => Promise<void>; h?: string }
>
const hydrateIdSelector = `[${hydrateIdAttribute}]`
function getMarker() {
const marker = document.querySelector(hydrateIdSelector)
if (!marker) {
throw new Error('Expected Hydrate marker to exist')
}
return marker
}
function InteractiveChild() {
const [count, setCount] = React.useState(0)
const [hydrated, setHydrated] = React.useState(false)
React.useEffect(() => {
setHydrated(true)
}, [])
return (
<button
data-testid="child"
data-hydrated={hydrated ? 'true' : 'false'}
onClick={() => setCount((prev) => prev + 1)}
>
{count}
</button>
)
}
function NamedInteractiveChild(props: { id: string }) {
const [hydrated, setHydrated] = React.useState(false)
React.useEffect(() => {
setHydrated(true)
}, [])
return (
<button
data-testid={`child-${props.id}`}
data-hydrated={hydrated ? 'true' : 'false'}
>
{props.id}
</button>
)
}
function createSuspendingChild() {
let resolve!: () => void
let resolved = false
const promise = new Promise<void>((resolvePromise) => {
resolve = () => {
resolved = true
resolvePromise()
}
})
function SuspendingChild() {
if (!resolved) {
throw promise
}
return <div data-testid="child">child</div>
}
return { resolve, SuspendingChild }
}
async function expectNoHydrationAfterDefaultIntentEvents() {
const marker = getMarker()
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'false',
)
await act(async () => {
fireEvent.pointerEnter(marker)
fireEvent.focusIn(marker)
fireEvent.pointerDown(marker)
fireEvent.click(marker)
await new Promise((resolve) => setTimeout(resolve, 20))
})
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'false',
)
}
async function fireIntent(event: () => void) {
await act(async () => {
event()
await Promise.resolve()
})
}
async function renderAsync(ui: React.ReactElement) {
await act(async () => {
render(ui)
await Promise.resolve()
})
}
async function hydrateFromServer(ui: React.ReactElement) {
vi.stubGlobal('window', undefined)
const html = renderToString(ui)
vi.unstubAllGlobals()
const container = document.createElement('div')
document.body.append(container)
container.innerHTML = html
let root!: ReturnType<typeof hydrateRoot>
await act(async () => {
root = hydrateRoot(container, ui)
await Promise.resolve()
})
return { container, html, root }
}
async function unmountHydratedRoot(
root: ReturnType<typeof hydrateRoot>,
container: Element,
) {
await act(async () => {
root.unmount()
})
container.remove()
}
afterEach(() => {
cleanup()
vi.unstubAllGlobals()
})
describe('Hydrate', () => {
it('uses a single custom interaction event instead of the default intent events', async () => {
const { container, html, root } = await hydrateFromServer(
<Hydrate
when={interaction({ events: 'dblclick' })}
fallback={<div data-testid="fallback">fallback</div>}
>
<InteractiveChild />
</Hydrate>,
)
try {
expect(html).toContain('data-testid="child"')
expect(html).not.toContain('data-testid="fallback"')
expect(screen.queryByTestId('fallback')).toBeNull()
await expectNoHydrationAfterDefaultIntentEvents()
await fireIntent(() =>
getMarker().dispatchEvent(
new MouseEvent('dblclick', { bubbles: true, cancelable: true }),
),
)
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
} finally {
await unmountHydratedRoot(root, container)
}
})
it('uses every event in a custom interaction event list', async () => {
const { container, root } = await hydrateFromServer(
<Hydrate
when={interaction({ events: ['contextmenu', 'dblclick'] })}
fallback={<div data-testid="fallback">fallback</div>}
>
<InteractiveChild />
</Hydrate>,
)
try {
expect(screen.queryByTestId('fallback')).toBeNull()
await expectNoHydrationAfterDefaultIntentEvents()
await fireIntent(() =>
getMarker().dispatchEvent(
new MouseEvent('contextmenu', { bubbles: true, cancelable: true }),
),
)
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
} finally {
await unmountHydratedRoot(root, container)
}
})
it('omits never content when mounted after the app is already hydrated', async () => {
await renderAsync(
<Hydrate when={never()}>
<InteractiveChild />
</Hydrate>,
)
expect(screen.queryByTestId('child')).toBeNull()
})
it('shows fallback for a client-only mount while children suspend', async () => {
const { resolve, SuspendingChild } = createSuspendingChild()
await renderAsync(
<Hydrate
when={load()}
fallback={<div data-testid="fallback">fallback</div>}
>
<SuspendingChild />
</Hydrate>,
)
expect(screen.getByTestId('fallback').textContent).toBe('fallback')
expect(screen.queryByTestId('child')).toBeNull()
await act(async () => {
resolve()
await Promise.resolve()
})
await screen.findByTestId('child')
expect(screen.queryByTestId('fallback')).toBeNull()
})
it('does not use fallback for an initial never boundary', async () => {
const { container, html, root } = await hydrateFromServer(
<Hydrate
when={never()}
fallback={<div data-testid="fallback">fallback</div>}
>
<InteractiveChild />
</Hydrate>,
)
try {
expect(html).toContain('data-testid="child"')
expect(html).not.toContain('data-testid="fallback"')
expect(screen.queryByTestId('fallback')).toBeNull()
fireEvent.click(screen.getByTestId('child'))
await new Promise((resolve) => setTimeout(resolve, 20))
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'false',
)
expect(screen.getByTestId('child').textContent).toBe('0')
} finally {
await unmountHydratedRoot(root, container)
}
})
it('keeps repeated split boundaries independently gated', async () => {
const { container, root } = await hydrateFromServer(
<>
<InternalHydrate when={interaction()} h="shared-boundary">
<NamedInteractiveChild id="one" />
</InternalHydrate>
<InternalHydrate when={interaction()} h="shared-boundary">
<NamedInteractiveChild id="two" />
</InternalHydrate>
</>,
)
try {
const markers = container.querySelectorAll(hydrateIdSelector)
expect(markers).toHaveLength(2)
expect(markers[0]!.getAttribute(hydrateIdAttribute)).not.toBe(
markers[1]!.getAttribute(hydrateIdAttribute),
)
expect(
screen.getByTestId('child-one').getAttribute('data-hydrated'),
).toBe('false')
expect(
screen.getByTestId('child-two').getAttribute('data-hydrated'),
).toBe('false')
await fireIntent(() =>
markers[0]!.dispatchEvent(
new MouseEvent('click', { bubbles: true, cancelable: true }),
),
)
await waitFor(() =>
expect(
screen.getByTestId('child-one').getAttribute('data-hydrated'),
).toBe('true'),
)
expect(
screen.getByTestId('child-two').getAttribute('data-hydrated'),
).toBe('false')
} finally {
await unmountHydratedRoot(root, container)
}
})
it('fires onHydrated once after the client hydration commit', async () => {
const onHydrated = vi.fn()
const app = (
<Hydrate when={load()} onHydrated={onHydrated}>
<div data-testid="child">child</div>
</Hydrate>
)
vi.stubGlobal('window', undefined)
const html = renderToString(app)
expect(html).toContain('child')
expect(onHydrated).not.toHaveBeenCalled()
vi.unstubAllGlobals()
const container = document.createElement('div')
document.body.append(container)
container.innerHTML = html
let root!: ReturnType<typeof hydrateRoot>
await act(async () => {
root = hydrateRoot(container, app)
})
await waitFor(() => expect(onHydrated).toHaveBeenCalledTimes(1))
fireEvent.click(screen.getByTestId('child'))
await new Promise((resolve) => setTimeout(resolve, 20))
expect(onHydrated).toHaveBeenCalledTimes(1)
await act(async () => {
root.unmount()
})
container.remove()
})
it('prefetches split children without hydrating the boundary', async () => {
const preload = vi.fn(() => Promise.resolve())
const { container, root } = await hydrateFromServer(
<InternalHydrate
when={interaction()}
prefetch={idle({ timeout: 1 })}
p={preload}
>
<InteractiveChild />
</InternalHydrate>,
)
try {
await waitFor(() => expect(preload).toHaveBeenCalledTimes(1))
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'false',
)
await fireIntent(() =>
getMarker().dispatchEvent(
new MouseEvent('click', { bubbles: true, cancelable: true }),
),
)
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
expect(preload).toHaveBeenCalledTimes(1)
} finally {
await unmountHydratedRoot(root, container)
}
})
it('does not evaluate dynamic when callbacks on the server', async () => {
const when = vi.fn(() => interaction({ events: 'dblclick' }))
vi.stubGlobal('window', undefined)
const html = renderToString(
<Hydrate when={when}>
<InteractiveChild />
</Hydrate>,
)
vi.unstubAllGlobals()
expect(when).not.toHaveBeenCalled()
expect(html).toContain('data-ts-hydrate-when="dynamic"')
const container = document.createElement('div')
document.body.append(container)
container.innerHTML = html
let root!: ReturnType<typeof hydrateRoot>
try {
await act(async () => {
root = hydrateRoot(
container,
<Hydrate when={when}>
<InteractiveChild />
</Hydrate>,
)
await Promise.resolve()
})
expect(when).toHaveBeenCalled()
await expectNoHydrationAfterDefaultIntentEvents()
await fireIntent(() =>
getMarker().dispatchEvent(
new MouseEvent('dblclick', { bubbles: true, cancelable: true }),
),
)
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
} finally {
await unmountHydratedRoot(root, container)
}
})
it('replays an interaction captured before the Hydrate component hydrates', async () => {
const when = () => interaction({ events: 'click' })
vi.stubGlobal('window', undefined)
const html = renderToString(
<Hydrate when={when}>
<InteractiveChild />
</Hydrate>,
)
vi.unstubAllGlobals()
const container = document.createElement('div')
document.body.append(container)
container.innerHTML = html
const button = container.querySelector('[data-testid="child"]')
if (!button) {
throw new Error('Expected server-rendered child button')
}
button.dispatchEvent(
new MouseEvent('click', { bubbles: true, cancelable: true }),
)
let root!: ReturnType<typeof hydrateRoot>
try {
await act(async () => {
root = hydrateRoot(
container,
<Hydrate when={when}>
<InteractiveChild />
</Hydrate>,
)
await Promise.resolve()
})
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
await waitFor(() =>
expect(screen.getByTestId('child').textContent).toBe('1'),
)
} finally {
await unmountHydratedRoot(root, container)
}
})
it('blocks hydration on awaited procedural prefetch work', async () => {
const preload = vi.fn(() => Promise.resolve())
let resolvePrefetch!: () => void
const prefetchBlocker = new Promise<void>((resolve) => {
resolvePrefetch = resolve
})
const waitReasons: Array<string> = []
const neverPrefetches = {
_t: 'idle',
_s: () => () => {},
} as HydrationPrefetchStrategy<'idle'>
const { container, root } = await hydrateFromServer(
<InternalHydrate
when={interaction()}
prefetch={async ({ waitFor, preload }) => {
waitReasons.push(await waitFor(neverPrefetches))
await preload()
await prefetchBlocker
}}
p={preload}
>
<InteractiveChild />
</InternalHydrate>,
)
try {
await fireIntent(() =>
getMarker().dispatchEvent(
new MouseEvent('click', { bubbles: true, cancelable: true }),
),
)
await waitFor(() => expect(waitReasons).toEqual(['hydrate']))
expect(preload).toHaveBeenCalledTimes(1)
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'false',
)
await act(async () => {
resolvePrefetch()
await prefetchBlocker
await Promise.resolve()
})
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
} finally {
await unmountHydratedRoot(root, container)
}
})
it('hydrates when a condition strategy changes after the initial render', async () => {
function ConditionHarness() {
const [ready, setReady] = React.useState(false)
return (
<>
<button data-testid="ready" onClick={() => setReady(true)}>
ready
</button>
<Hydrate when={condition(ready)}>
<InteractiveChild />
</Hydrate>
</>
)
}
const { container, root } = await hydrateFromServer(<ConditionHarness />)
try {
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'false',
)
await act(async () => {
fireEvent.click(screen.getByTestId('ready'))
await Promise.resolve()
})
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
} finally {
await unmountHydratedRoot(root, container)
}
})
it('does not block hydration on fire-and-forget procedural prefetch work', async () => {
let resolvePrefetch!: () => void
const prefetchBlocker = new Promise<void>((resolve) => {
resolvePrefetch = resolve
})
const { container, root } = await hydrateFromServer(
<InternalHydrate
when={interaction()}
prefetch={() => {
void prefetchBlocker
}}
>
<InteractiveChild />
</InternalHydrate>,
)
try {
await fireIntent(() =>
getMarker().dispatchEvent(
new MouseEvent('click', { bubbles: true, cancelable: true }),
),
)
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
await act(async () => {
resolvePrefetch()
await prefetchBlocker
})
} finally {
await unmountHydratedRoot(root, container)
}
})
it('aborts procedural prefetch when the boundary unmounts', async () => {
const signals: Array<AbortSignal> = []
const { container, root } = await hydrateFromServer(
<InternalHydrate
when={interaction()}
prefetch={({ signal }) => {
signals.push(signal)
return new Promise<void>(() => {})
}}
>
<InteractiveChild />
</InternalHydrate>,
)
expect(signals).toHaveLength(1)
expect(signals[0]!.aborted).toBe(false)
await unmountHydratedRoot(root, container)
expect(signals[0]!.aborted).toBe(true)
})
it('delegates nested interaction boundaries at runtime', async () => {
const { container, root } = await hydrateFromServer(
<Hydrate when={idle({ timeout: 1000 })}>
<Hydrate when={interaction()}>
<InteractiveChild />
</Hydrate>
</Hydrate>,
)
try {
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'false',
)
await fireIntent(() => {
fireEvent.click(screen.getByTestId('child'))
})
await waitFor(() =>
expect(screen.getByTestId('child').getAttribute('data-hydrated')).toBe(
'true',
),
)
} finally {
await unmountHydratedRoot(root, container)
}
})
})
+2
-0
export { StartClient } from './StartClient.js';
export { hydrateStart } from './hydrateStart.js';
export { Hydrate } from './Hydrate.js';
export type { HydrateOptions, HydrateProps, HydrateWhen, HydrationInteractionEvent, HydrationInteractionEvents, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationStrategy, HydrationWhen, } from './Hydrate.js';
+3
-1

@@ -0,3 +1,5 @@

"use client";
import { hydrateStart } from "./hydrateStart.js";
import { StartClient } from "./StartClient.js";
export { StartClient, hydrateStart };
import { Hydrate } from "./Hydrate.js";
export { Hydrate, StartClient, hydrateStart };
{
"name": "@tanstack/react-start-client",
"version": "1.167.4",
"version": "1.168.0",
"description": "Modern and scalable routing for React applications",

@@ -35,2 +35,8 @@ "author": "Tanner Linsley",

},
"./hydration": {
"import": {
"types": "./dist/esm/hydration.d.ts",
"default": "./dist/esm/hydration.js"
}
},
"./package.json": "./package.json"

@@ -47,5 +53,5 @@ },

"dependencies": {
"@tanstack/react-router": "1.170.4",
"@tanstack/router-core": "1.171.2",
"@tanstack/start-client-core": "1.169.4"
"@tanstack/react-router": "1.170.5",
"@tanstack/router-core": "1.171.3",
"@tanstack/start-client-core": "1.170.0"
},

@@ -52,0 +58,0 @@ "devDependencies": {

@@ -0,2 +1,18 @@

'use client'
export { StartClient } from './StartClient'
export { hydrateStart } from './hydrateStart'
export { Hydrate } from './Hydrate'
export type {
HydrateOptions,
HydrateProps,
HydrateWhen,
HydrationInteractionEvent,
HydrationInteractionEvents,
HydrationPrefetchContext,
HydrationPrefetchFunction,
HydrationPrefetchStrategy,
HydrationPrefetchWaitReason,
HydrationStrategy,
HydrationWhen,
} from './Hydrate'