'use client'
import * as React from 'react'
import { isServer } from '@tanstack/router-core/isServer'
import { useRouter } from './useRouter'
import { useHydrated } from './ClientOnly'
import type { RouterManagedTag } from '@tanstack/router-core'
const INLINE_CSS_HYDRATION_ATTR = 'data-tsr-inline-css'
interface ScriptAttrs {
[key: string]: string | boolean | undefined
src?: string
suppressHydrationWarning?: boolean
}
const noopScriptHandler = () => {}
function setScriptAttrs(
script: HTMLScriptElement,
attrs: ScriptAttrs | undefined,
) {
if (!attrs) {
return
}
for (const [key, value] of Object.entries(attrs)) {
if (
key !== 'suppressHydrationWarning' &&
value !== undefined &&
value !== false
) {
script.setAttribute(key, typeof value === 'boolean' ? '' : String(value))
}
}
}
export function Asset(
asset: RouterManagedTag & {
nonce?: string
preventScriptHoist?: boolean
},
): React.ReactElement | null {
const { attrs, children, nonce, preventScriptHoist } = asset
// React 19 compares this object by reference before assigning innerHTML.
const innerHTML = React.useMemo(
() => (children === undefined ? undefined : { __html: children }),
[children],
)
switch (asset.tag) {
case 'title':
return (
{children}
)
case 'meta':
return
case 'link':
return (
)
case 'style':
if (
asset.inlineCss &&
(process.env.TSS_INLINE_CSS_ENABLED === 'true' ||
(process.env.TSS_INLINE_CSS_ENABLED === undefined && isServer))
) {
return (
{children}
)
}
return (
)
case 'script':
return (
)
default:
return null
}
}
function InlineCssStyle({
attrs,
children,
nonce,
}: {
attrs?: Record
children?: RouterManagedTag['children']
nonce?: string
}) {
const isInlineCssPlaceholder = children === undefined
const [hydratedInlineCss] = React.useState(() => {
if (!isInlineCssPlaceholder || typeof document === 'undefined') {
return undefined
}
return (
document.querySelector(
`style[${INLINE_CSS_HYDRATION_ATTR}]`,
)?.textContent ?? undefined
)
})
const html = isInlineCssPlaceholder
? (hydratedInlineCss ?? '')
: (children ?? '')
const innerHTML = React.useMemo(() => ({ __html: html }), [html])
return (
)
}
function Script({
attrs,
children,
preventScriptHoist,
}: {
attrs?: ScriptAttrs
children?: string
preventScriptHoist?: boolean
}) {
const router = useRouter()
const hydrated = useHydrated()
const innerHTML = React.useMemo(
() => (children === undefined ? undefined : { __html: children }),
[children],
)
const dataScript =
typeof attrs?.type === 'string' &&
attrs.type !== '' &&
attrs.type !== 'text/javascript' &&
attrs.type !== 'module'
if (
process.env.NODE_ENV !== 'production' &&
attrs?.src &&
typeof children === 'string' &&
children.trim().length
) {
console.warn(
'[TanStack Router]