@tanstack/react-start-client 1.167.4 → 1.168.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/esm/GenericHydrate.d.ts +3 -0
  2. package/dist/esm/GenericHydrate.js +243 -0
  3. package/dist/esm/GenericHydrate.js.map +1 -0
  4. package/dist/esm/Hydrate.d.ts +31 -0
  5. package/dist/esm/Hydrate.js +34 -0
  6. package/dist/esm/Hydrate.js.map +1 -0
  7. package/dist/esm/hydration/generic.d.ts +7 -0
  8. package/dist/esm/hydration/generic.js +20 -0
  9. package/dist/esm/hydration/generic.js.map +1 -0
  10. package/dist/esm/hydration/idle.d.ts +3 -0
  11. package/dist/esm/hydration/idle.js +12 -0
  12. package/dist/esm/hydration/idle.js.map +1 -0
  13. package/dist/esm/hydration/load.d.ts +5 -0
  14. package/dist/esm/hydration/load.js +33 -0
  15. package/dist/esm/hydration/load.js.map +1 -0
  16. package/dist/esm/hydration/never.d.ts +4 -0
  17. package/dist/esm/hydration/never.js +56 -0
  18. package/dist/esm/hydration/never.js.map +1 -0
  19. package/dist/esm/hydration/visible.d.ts +5 -0
  20. package/dist/esm/hydration/visible.js +94 -0
  21. package/dist/esm/hydration/visible.js.map +1 -0
  22. package/dist/esm/hydration.d.ts +7 -0
  23. package/dist/esm/hydration.js +7 -0
  24. package/dist/esm/index.d.ts +2 -0
  25. package/dist/esm/index.js +3 -1
  26. package/dist/esm/tests/Hydrate.test-d.d.ts +1 -0
  27. package/dist/esm/tests/Hydrate.test.d.ts +1 -0
  28. package/package.json +10 -4
  29. package/src/GenericHydrate.tsx +436 -0
  30. package/src/Hydrate.tsx +107 -0
  31. package/src/hydration/generic.ts +43 -0
  32. package/src/hydration/idle.ts +22 -0
  33. package/src/hydration/load.tsx +49 -0
  34. package/src/hydration/never.tsx +97 -0
  35. package/src/hydration/visible.tsx +139 -0
  36. package/src/hydration.ts +22 -0
  37. package/src/index.tsx +16 -0
  38. package/src/tests/Hydrate.test-d.tsx +147 -0
  39. package/src/tests/Hydrate.test.tsx +676 -0
@@ -0,0 +1 @@
1
+ {"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"}
@@ -0,0 +1,7 @@
1
+ export { condition, interaction, media } from './hydration/generic.js';
2
+ export { idle } from './hydration/idle.js';
3
+ export { load } from './hydration/load.js';
4
+ export { never } from './hydration/never.js';
5
+ export { visible } from './hydration/visible.js';
6
+ export type { HydrationCondition, HydrationInteractionEvent, HydrationInteractionEvents, IdleHydrationOptions, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchWhen, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationStrategyTypes, HydrationWhen, VisibleHydrationOptions, } from '@tanstack/start-client-core/hydration';
7
+ export type { HydrationStrategy, ReactHydrationStrategy } from './Hydrate.js';
@@ -0,0 +1,7 @@
1
+ "use client";
2
+ import { condition, interaction, media } from "./hydration/generic.js";
3
+ import { idle } from "./hydration/idle.js";
4
+ import { load } from "./hydration/load.js";
5
+ import { never } from "./hydration/never.js";
6
+ import { visible } from "./hydration/visible.js";
7
+ export { condition, idle, interaction, load, media, never, visible };
@@ -1,2 +1,4 @@
1
1
  export { StartClient } from './StartClient.js';
2
2
  export { hydrateStart } from './hydrateStart.js';
3
+ export { Hydrate } from './Hydrate.js';
4
+ export type { HydrateOptions, HydrateProps, HydrateWhen, HydrationInteractionEvent, HydrationInteractionEvents, HydrationPrefetchContext, HydrationPrefetchFunction, HydrationPrefetchStrategy, HydrationPrefetchWaitReason, HydrationStrategy, HydrationWhen, } from './Hydrate.js';
package/dist/esm/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ "use client";
1
2
  import { hydrateStart } from "./hydrateStart.js";
2
3
  import { StartClient } from "./StartClient.js";
3
- export { StartClient, hydrateStart };
4
+ import { Hydrate } from "./Hydrate.js";
5
+ export { Hydrate, StartClient, hydrateStart };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-start-client",
3
- "version": "1.167.4",
3
+ "version": "1.168.0",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -32,6 +32,12 @@
32
32
  "default": "./dist/esm/index.js"
33
33
  }
34
34
  },
35
+ "./hydration": {
36
+ "import": {
37
+ "types": "./dist/esm/hydration.d.ts",
38
+ "default": "./dist/esm/hydration.js"
39
+ }
40
+ },
35
41
  "./package.json": "./package.json"
36
42
  },
37
43
  "sideEffects": false,
@@ -43,9 +49,9 @@
43
49
  "node": ">=22.12.0"
44
50
  },
45
51
  "dependencies": {
46
- "@tanstack/react-router": "1.170.4",
47
- "@tanstack/router-core": "1.171.2",
48
- "@tanstack/start-client-core": "1.169.4"
52
+ "@tanstack/react-router": "1.170.5",
53
+ "@tanstack/router-core": "1.171.3",
54
+ "@tanstack/start-client-core": "1.170.0"
49
55
  },
50
56
  "devDependencies": {
51
57
  "@testing-library/react": "^16.2.0",
@@ -0,0 +1,436 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+
5
+ import { reactUse, useHydrated, useLayoutEffect } from '@tanstack/react-router'
6
+ import { isServer } from '@tanstack/router-core/isServer'
7
+ import {
8
+ hydrateIdAttribute,
9
+ hydrateWhenAttribute,
10
+ } from '@tanstack/start-client-core/hydration/constants'
11
+ import {
12
+ createResolvedGate,
13
+ getFallbackHtml,
14
+ getOrCreateGate,
15
+ onGateResolve,
16
+ releaseGate,
17
+ runHydrationStrategyCleanup,
18
+ saveFallbackHtml,
19
+ waitForHydrationPrefetchStrategy,
20
+ } from '@tanstack/start-client-core/hydration/runtime'
21
+ import { listenForDelegatedHydrationIntent } from '@tanstack/start-client-core/hydration'
22
+ import type {
23
+ HydrationRuntimeContext,
24
+ HydrationStrategy,
25
+ HydrationWhen,
26
+ } from '@tanstack/start-client-core/hydration'
27
+ import type { HydrationGateRecord } from '@tanstack/start-client-core/hydration/runtime'
28
+ import type { HydrateProps, InternalHydrateProps } from './Hydrate'
29
+
30
+ type Gate = HydrationGateRecord & { promise: Promise<void> }
31
+ type PrefetchController = {
32
+ abortController: AbortController
33
+ hydrationRequested: boolean
34
+ hydrationListeners: Set<() => void>
35
+ hydrationResolvePending: boolean
36
+ started: boolean
37
+ promise?: Promise<void>
38
+ cleanup?: () => void
39
+ }
40
+
41
+ const dynamicType = 'dynamic'
42
+ const dynamicHydrateStrategy = {
43
+ _t: dynamicType,
44
+ _d: () => true,
45
+ } satisfies HydrationStrategy<typeof dynamicType, false>
46
+
47
+ function shouldDeferHydration(strategy: HydrationStrategy) {
48
+ return strategy._d ? strategy._d() : strategy._t !== 'load'
49
+ }
50
+
51
+ function useLatest<T>(value: T) {
52
+ const ref = React.useRef(value)
53
+ ref.current = value
54
+ return ref
55
+ }
56
+
57
+ function useHydrationGate(props: InternalHydrateProps) {
58
+ const hydrated = useHydrated()
59
+ const reactId = React.useId()
60
+ const id = props.h ? `${props.h}${reactId}` : reactId
61
+ const when = props.when
62
+ const isDynamicHydrate = typeof when === 'function'
63
+ const dynamicHydrateStrategyRef = React.useRef<HydrationStrategy | undefined>(
64
+ undefined,
65
+ )
66
+ if (isDynamicHydrate) {
67
+ dynamicHydrateStrategyRef.current ??=
68
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
69
+ (isServer ?? typeof window === 'undefined')
70
+ ? dynamicHydrateStrategy
71
+ : when()
72
+ }
73
+ const hydrateStrategy = isDynamicHydrate
74
+ ? dynamicHydrateStrategyRef.current!
75
+ : when
76
+ const markerHydrateType: HydrationWhen = isDynamicHydrate
77
+ ? dynamicType
78
+ : hydrateStrategy._t!
79
+ const [prefetchError, setPrefetchError] = React.useState<unknown>()
80
+ const latestRef = useLatest({
81
+ prefetch: props.prefetch,
82
+ preload: props.p,
83
+ })
84
+ const gateRef = React.useRef<HydrationGateRecord | undefined>(undefined)
85
+ const markerElementRef = React.useRef<HTMLDivElement | null>(null)
86
+ const shouldPreserveServerHTMLRef = React.useRef<boolean | undefined>(
87
+ undefined,
88
+ )
89
+ const shouldDeferInitialHydrationRef = React.useRef<boolean | undefined>(
90
+ undefined,
91
+ )
92
+ const didPrefetchRef = React.useRef(false)
93
+ const prefetchControllerRef = React.useRef<PrefetchController | undefined>(
94
+ undefined,
95
+ )
96
+
97
+ prefetchControllerRef.current ??= {
98
+ abortController: new AbortController(),
99
+ hydrationRequested: false,
100
+ hydrationListeners: new Set<() => void>(),
101
+ hydrationResolvePending: false,
102
+ started: false,
103
+ }
104
+
105
+ shouldPreserveServerHTMLRef.current ??=
106
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
107
+ (isServer ?? typeof window === 'undefined') || !hydrated
108
+ shouldDeferInitialHydrationRef.current ??=
109
+ !hydrated && shouldDeferHydration(hydrateStrategy)
110
+
111
+ if (!gateRef.current) {
112
+ gateRef.current =
113
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
114
+ (isServer ?? typeof window === 'undefined')
115
+ ? createResolvedGate(id, hydrateStrategy._t!)
116
+ : getOrCreateGate(id, hydrateStrategy._t!)
117
+ }
118
+
119
+ gateRef.current.when = hydrateStrategy._t!
120
+
121
+ if (
122
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
123
+ !(isServer ?? typeof window === 'undefined') &&
124
+ hydrateStrategy._t !== 'never' &&
125
+ (!shouldDeferInitialHydrationRef.current ||
126
+ !shouldDeferHydration(hydrateStrategy))
127
+ ) {
128
+ gateRef.current.resolve()
129
+ }
130
+
131
+ const markerRef = React.useCallback(
132
+ (element: HTMLDivElement | null) => {
133
+ markerElementRef.current = element
134
+ if (element) {
135
+ if (
136
+ hydrateStrategy._t === 'never' &&
137
+ !shouldPreserveServerHTMLRef.current
138
+ ) {
139
+ element.replaceChildren()
140
+ }
141
+ saveFallbackHtml(id, element)
142
+ }
143
+ },
144
+ [hydrateStrategy._t, id],
145
+ )
146
+
147
+ React.useEffect(() => {
148
+ const gate = gateRef.current!
149
+ return () => {
150
+ const controller = prefetchControllerRef.current
151
+ controller?.abortController.abort()
152
+ controller?.cleanup?.()
153
+ controller?.hydrationListeners.clear()
154
+ releaseGate(gate)
155
+ }
156
+ }, [])
157
+
158
+ React.useEffect(() => {
159
+ if (
160
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
161
+ (isServer ?? typeof window === 'undefined') ||
162
+ !latestRef.current.prefetch
163
+ ) {
164
+ return
165
+ }
166
+
167
+ const controller = prefetchControllerRef.current!
168
+ if (controller.started) return
169
+ controller.started = true
170
+
171
+ const onHydrate = (listener: () => void) => {
172
+ if (controller.hydrationRequested) {
173
+ listener()
174
+ return () => {}
175
+ }
176
+
177
+ controller.hydrationListeners.add(listener)
178
+ return () => {
179
+ controller.hydrationListeners.delete(listener)
180
+ }
181
+ }
182
+
183
+ const preload = () => latestRef.current.preload?.() ?? Promise.resolve()
184
+ const prefetchInput = latestRef.current.prefetch
185
+
186
+ if (typeof prefetchInput === 'function') {
187
+ const promise = Promise.resolve()
188
+ .then(() =>
189
+ prefetchInput({
190
+ element: markerElementRef.current,
191
+ signal: controller.abortController.signal,
192
+ preload,
193
+ waitFor: (strategy) =>
194
+ waitForHydrationPrefetchStrategy(strategy, {
195
+ element: markerElementRef.current,
196
+ signal: controller.abortController.signal,
197
+ onHydrate,
198
+ }),
199
+ }),
200
+ )
201
+ .then(() => undefined)
202
+
203
+ controller.promise = promise
204
+ promise.catch((error) => {
205
+ if (!controller.abortController.signal.aborted) {
206
+ setPrefetchError(error)
207
+ }
208
+ })
209
+ return
210
+ }
211
+
212
+ if (!latestRef.current.preload) return
213
+
214
+ const prefetch = () => {
215
+ if (didPrefetchRef.current) return
216
+ didPrefetchRef.current = true
217
+ void preload()
218
+ }
219
+
220
+ controller.cleanup = runHydrationStrategyCleanup(
221
+ prefetchInput._s?.({
222
+ element: markerElementRef.current,
223
+ prefetch,
224
+ }),
225
+ )
226
+ }, [hydrateStrategy, latestRef])
227
+
228
+ useLayoutEffect(() => {
229
+ const gate = gateRef.current!
230
+ if (
231
+ !shouldDeferInitialHydrationRef.current ||
232
+ hydrateStrategy._t === 'never'
233
+ ) {
234
+ return
235
+ }
236
+
237
+ if (gate.resolved) {
238
+ return
239
+ }
240
+
241
+ const cleanups: Array<() => void> = []
242
+ let removeResolveListener = () => {}
243
+ let disposed = false
244
+ const resolveGate = gate.resolve
245
+
246
+ const cleanup = () => {
247
+ if (disposed) return
248
+ disposed = true
249
+ if (gate.resolve === requestHydration) {
250
+ gate.resolve = resolveGate
251
+ }
252
+ removeResolveListener()
253
+ cleanups.forEach((fn) => fn())
254
+ }
255
+
256
+ const addCleanup = (fn: void | (() => void)) => {
257
+ if (!fn) return
258
+ if (disposed || gate.resolved) {
259
+ fn()
260
+ return
261
+ }
262
+ cleanups.push(fn)
263
+ }
264
+
265
+ const requestHydration = () => {
266
+ const controller = prefetchControllerRef.current!
267
+ if (!controller.hydrationRequested) {
268
+ controller.hydrationRequested = true
269
+ controller.hydrationListeners.forEach((listener) => listener())
270
+ controller.hydrationListeners.clear()
271
+ }
272
+
273
+ if (!controller.promise) {
274
+ resolveGate()
275
+ return
276
+ }
277
+ if (controller.hydrationResolvePending) return
278
+ controller.hydrationResolvePending = true
279
+
280
+ controller.promise.then(
281
+ () => resolveGate(),
282
+ (error) => {
283
+ if (!controller.abortController.signal.aborted) {
284
+ setPrefetchError(error)
285
+ }
286
+ },
287
+ )
288
+ }
289
+
290
+ gate.resolve = requestHydration
291
+ removeResolveListener = onGateResolve(gate, cleanup)
292
+
293
+ const context: HydrationRuntimeContext = {
294
+ element: markerElementRef.current,
295
+ gate,
296
+ }
297
+ addCleanup(runHydrationStrategyCleanup(hydrateStrategy._s?.(context)))
298
+
299
+ if (hydrateStrategy._t !== 'interaction') {
300
+ addCleanup(
301
+ runHydrationStrategyCleanup(
302
+ markerElementRef.current
303
+ ? listenForDelegatedHydrationIntent(
304
+ markerElementRef.current,
305
+ context,
306
+ )
307
+ : undefined,
308
+ ),
309
+ )
310
+ }
311
+
312
+ return cleanup
313
+ }, [hydrateStrategy, latestRef])
314
+
315
+ return {
316
+ gate: gateRef.current,
317
+ markerRef,
318
+ markerElementRef,
319
+ hydrateStrategy,
320
+ markerHydrateType,
321
+ prefetchError,
322
+ shouldPreserveServerHTML: shouldPreserveServerHTMLRef.current,
323
+ }
324
+ }
325
+
326
+ function HydrationGate(props: { gate: Gate; children: React.ReactNode }) {
327
+ if (
328
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
329
+ isServer ??
330
+ typeof window === 'undefined'
331
+ ) {
332
+ return props.children as React.JSX.Element
333
+ }
334
+
335
+ if (props.gate.resolved) {
336
+ return props.children as React.JSX.Element
337
+ }
338
+
339
+ if (!reactUse) {
340
+ throw props.gate.promise
341
+ }
342
+
343
+ reactUse(props.gate.promise)
344
+
345
+ return props.children as React.JSX.Element
346
+ }
347
+
348
+ function HydratedBoundary(props: {
349
+ id: string
350
+ onHydrated?: () => void
351
+ onStrategyHydrated?: (id: string) => void
352
+ children: React.ReactNode
353
+ }) {
354
+ const { id, onHydrated, onStrategyHydrated } = props
355
+ const didHydrateRef = React.useRef(false)
356
+
357
+ React.useEffect(() => {
358
+ if (didHydrateRef.current) return
359
+ didHydrateRef.current = true
360
+ onHydrated?.()
361
+ onStrategyHydrated?.(id)
362
+ }, [id, onHydrated, onStrategyHydrated])
363
+
364
+ return props.children as React.JSX.Element
365
+ }
366
+
367
+ export function GenericHydrate(props: HydrateProps): React.JSX.Element {
368
+ const internalProps = props as InternalHydrateProps
369
+ const {
370
+ gate,
371
+ hydrateStrategy,
372
+ markerHydrateType,
373
+ markerElementRef,
374
+ markerRef,
375
+ prefetchError,
376
+ shouldPreserveServerHTML,
377
+ } = useHydrationGate(internalProps)
378
+ if (prefetchError) throw prefetchError
379
+
380
+ const fallback = shouldPreserveServerHTML
381
+ ? (() => {
382
+ const html = getFallbackHtml(gate.id)
383
+ return html ? (
384
+ <div
385
+ style={{ display: 'contents' }}
386
+ dangerouslySetInnerHTML={{ __html: html }}
387
+ />
388
+ ) : null
389
+ })()
390
+ : (props.fallback ?? null)
391
+ const markerAttributes =
392
+ markerHydrateType === dynamicType ? undefined : hydrateStrategy._a?.()
393
+
394
+ const hydrateType = hydrateStrategy._t!
395
+
396
+ if (hydrateType === 'never' && !shouldPreserveServerHTML) {
397
+ return (
398
+ <div
399
+ ref={markerRef}
400
+ {...{
401
+ [hydrateIdAttribute]: gate.id,
402
+ [hydrateWhenAttribute]: markerHydrateType,
403
+ ...markerAttributes,
404
+ }}
405
+ >
406
+ {props.fallback ?? null}
407
+ </div>
408
+ )
409
+ }
410
+
411
+ return (
412
+ <div
413
+ ref={markerRef}
414
+ {...{
415
+ [hydrateIdAttribute]: gate.id,
416
+ [hydrateWhenAttribute]: markerHydrateType,
417
+ ...markerAttributes,
418
+ }}
419
+ >
420
+ <React.Suspense fallback={fallback}>
421
+ <HydrationGate gate={gate}>
422
+ <HydratedBoundary
423
+ id={gate.id}
424
+ onHydrated={props.onHydrated}
425
+ onStrategyHydrated={(id) => {
426
+ markerElementRef.current?.removeAttribute(hydrateWhenAttribute)
427
+ hydrateStrategy._o?.(id)
428
+ }}
429
+ >
430
+ {props.children}
431
+ </HydratedBoundary>
432
+ </HydrationGate>
433
+ </React.Suspense>
434
+ </div>
435
+ )
436
+ }
@@ -0,0 +1,107 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+
5
+ import { isServer } from '@tanstack/router-core/isServer'
6
+ import type {
7
+ HydrationStrategy as CoreHydrationStrategy,
8
+ HydrationPrefetchFunction,
9
+ HydrationPrefetchStrategy,
10
+ HydrationWhen,
11
+ } from '@tanstack/start-client-core/hydration'
12
+
13
+ export type {
14
+ HydrationInteractionEvent,
15
+ HydrationInteractionEvents,
16
+ HydrationPrefetchContext,
17
+ HydrationPrefetchFunction,
18
+ HydrationPrefetchStrategy,
19
+ HydrationPrefetchWaitReason,
20
+ HydrationWhen,
21
+ } from '@tanstack/start-client-core/hydration'
22
+
23
+ export type ReactHydrationStrategy<
24
+ TWhen extends HydrationWhen = HydrationWhen,
25
+ TCanPrefetch extends boolean = boolean,
26
+ > = CoreHydrationStrategy<TWhen, TCanPrefetch> & {
27
+ _h: (this: ReactHydrationStrategy, props: HydrateProps) => React.JSX.Element
28
+ }
29
+
30
+ export type HydrationStrategy<
31
+ TWhen extends HydrationWhen = HydrationWhen,
32
+ TCanPrefetch extends boolean = boolean,
33
+ > = ReactHydrationStrategy<TWhen, TCanPrefetch>
34
+
35
+ export type HydrateWhen =
36
+ | ReactHydrationStrategy
37
+ | (() => ReactHydrationStrategy)
38
+
39
+ type HydrateCommonOptions = {
40
+ when: HydrateWhen
41
+ fallback?: React.ReactNode
42
+ onHydrated?: () => void
43
+ }
44
+
45
+ export type HydrateOptions =
46
+ | (HydrateCommonOptions & {
47
+ prefetch?: never
48
+ split?: boolean
49
+ })
50
+ | (HydrateCommonOptions & {
51
+ prefetch: HydrationPrefetchStrategy
52
+ split?: true
53
+ })
54
+ | (HydrateCommonOptions & {
55
+ prefetch: HydrationPrefetchFunction
56
+ split?: boolean
57
+ })
58
+
59
+ export type HydrateProps = HydrateOptions & {
60
+ children: React.ReactNode
61
+ }
62
+
63
+ export type InternalHydrateProps = HydrateProps & {
64
+ h?: string
65
+ p?: () => Promise<void>
66
+ }
67
+
68
+ const dynamicType = 'dynamic'
69
+ const hydrateIdAttribute = 'data-ts-hydrate-id'
70
+ const hydrateWhenAttribute = 'data-ts-hydrate-when'
71
+
72
+ /* @__NO_SIDE_EFFECTS__ */
73
+ function ServerDynamicHydrate(props: HydrateProps): React.JSX.Element {
74
+ const internalProps = props as InternalHydrateProps
75
+ const reactId = React.useId()
76
+ const id = internalProps.h ? `${internalProps.h}${reactId}` : reactId
77
+
78
+ return (
79
+ <div
80
+ {...{
81
+ [hydrateIdAttribute]: id,
82
+ [hydrateWhenAttribute]: dynamicType,
83
+ }}
84
+ >
85
+ <React.Suspense fallback={props.fallback ?? null}>
86
+ {props.children}
87
+ </React.Suspense>
88
+ </div>
89
+ )
90
+ }
91
+
92
+ /* @__NO_SIDE_EFFECTS__ */
93
+ export function Hydrate(props: HydrateProps): React.JSX.Element {
94
+ if (typeof props.when === 'function') {
95
+ if (
96
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
97
+ isServer ??
98
+ typeof window === 'undefined'
99
+ ) {
100
+ return <ServerDynamicHydrate {...props} />
101
+ }
102
+
103
+ return props.when()._h(props)
104
+ }
105
+
106
+ return props.when._h(props)
107
+ }
@@ -0,0 +1,43 @@
1
+ 'use client'
2
+
3
+ import {
4
+ condition as coreCondition,
5
+ interaction as coreInteraction,
6
+ media as coreMedia,
7
+ withHydrationRenderer,
8
+ } from '@tanstack/start-client-core/hydration'
9
+ import { GenericHydrate } from '../GenericHydrate'
10
+ import type {
11
+ HydrationCondition,
12
+ HydrationInteractionEvents,
13
+ HydrationPrefetchStrategy,
14
+ } from '@tanstack/start-client-core/hydration'
15
+ import type { ReactHydrationStrategy } from '../Hydrate'
16
+
17
+ /* @__NO_SIDE_EFFECTS__ */
18
+ export function media(
19
+ query: string,
20
+ ): ReactHydrationStrategy<'media', true> & HydrationPrefetchStrategy<'media'> {
21
+ return /* @__PURE__ */ withHydrationRenderer(coreMedia(query), GenericHydrate)
22
+ }
23
+
24
+ /* @__NO_SIDE_EFFECTS__ */
25
+ export function condition(
26
+ condition: HydrationCondition,
27
+ ): ReactHydrationStrategy<'condition', false> {
28
+ return /* @__PURE__ */ withHydrationRenderer(
29
+ coreCondition(condition),
30
+ GenericHydrate,
31
+ )
32
+ }
33
+
34
+ /* @__NO_SIDE_EFFECTS__ */
35
+ export function interaction(options?: {
36
+ events?: HydrationInteractionEvents
37
+ }): ReactHydrationStrategy<'interaction', true> &
38
+ HydrationPrefetchStrategy<'interaction'> {
39
+ return /* @__PURE__ */ withHydrationRenderer(
40
+ coreInteraction(options),
41
+ GenericHydrate,
42
+ )
43
+ }
@@ -0,0 +1,22 @@
1
+ 'use client'
2
+
3
+ import {
4
+ idle as coreIdle,
5
+ withHydrationRenderer,
6
+ } from '@tanstack/start-client-core/hydration'
7
+ import { GenericHydrate } from '../GenericHydrate'
8
+ import type {
9
+ HydrationPrefetchStrategy,
10
+ IdleHydrationOptions,
11
+ } from '@tanstack/start-client-core/hydration'
12
+ import type { ReactHydrationStrategy } from '../Hydrate'
13
+
14
+ /* @__NO_SIDE_EFFECTS__ */
15
+ export function idle(
16
+ options: IdleHydrationOptions = {},
17
+ ): ReactHydrationStrategy<'idle', true> & HydrationPrefetchStrategy<'idle'> {
18
+ return /* @__PURE__ */ withHydrationRenderer(
19
+ coreIdle(options),
20
+ GenericHydrate,
21
+ )
22
+ }