Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/remove-link-transition-state.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@tanstack/react-router': patch
'@tanstack/solid-router': patch
'@tanstack/vue-router': patch
---

remove the undocumented Link `isTransitioning` state and `data-transitioning` attribute
19 changes: 1 addition & 18 deletions packages/react-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,7 +2,6 @@

import * as React from 'react'
import { useStore } from '@tanstack/react-store'
import { flushSync } from 'react-dom'
import {
deepEqual,
exactPathTest,
Expand DownExpand Up@@ -592,8 +591,6 @@ export function useLinkProps<
...resolvedInactiveProps.style,
}

// eslint-disable-next-line react-hooks/rules-of-hooks
const [isTransitioning, setIsTransitioning] = React.useState(false)
// eslint-disable-next-line react-hooks/rules-of-hooks
const hasRenderFetched = React.useRef(false)

Expand DownExpand Up@@ -660,15 +657,6 @@ export function useLinkProps<
) {
e.preventDefault()

flushSync(() => {
setIsTransitioning(true)
})

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -756,15 +744,13 @@ export function useLinkProps<
...(resolvedClassName && { className: resolvedClassName }),
...(disabled && STATIC_DISABLED_PROPS),
...(isActive && STATIC_ACTIVE_PROPS),
...(isHydrated && isTransitioning && STATIC_TRANSITIONING_PROPS),
}
}

const STATIC_EMPTY_OBJECT = {}
const STATIC_ACTIVE_OBJECT = { className: 'active' }
const STATIC_DISABLED_PROPS = { role: 'link', 'aria-disabled': true }
const STATIC_ACTIVE_PROPS = { 'data-status': 'active', 'aria-current': 'page' }
const STATIC_TRANSITIONING_PROPS = { 'data-transitioning': 'transitioning' }

const timeoutMap = new WeakMap<EventTarget, ReturnType<typeof setTimeout>>()

Expand DownExpand Up@@ -865,10 +851,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| React.ReactNode
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => React.ReactNode)
| ((state: { isActive: boolean }) => React.ReactNode)
}

type LinkComponentReactProps<TComp> = Omit<
Expand Down
20 changes: 1 addition & 19 deletions packages/solid-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,7 +43,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): Solid.ComponentProps<'a'> {
const router = useRouter()
const [isTransitioning, setIsTransitioning] = Solid.createSignal(false)
const shouldHydrateHash = !isServer && !!router.options.ssr
const hasHydrated = useHydrated()

Expand DownExpand Up@@ -324,13 +323,6 @@ export function useLinkProps<
) {
e.preventDefault()

setIsTransitioning(true)

const unsub = router.subscribe('onResolved', () => {
unsub()
setIsTransitioning(false)
})

// All is well? Navigate!
// N.B. we don't call `router.commitLocation(next) here because we want to run `validateSearch` before committing
router.navigate({
Expand DownExpand Up@@ -435,7 +427,6 @@ export function useLinkProps<
disabled: !!local.disabled,
target: local.target,
...(local.disabled && STATIC_DISABLED_PROPS),
...(isTransitioning() && STATIC_TRANSITIONING_ATTRIBUTES),
}

if (simpleStyling()) {
Expand DownExpand Up@@ -490,9 +481,6 @@ const STATIC_ACTIVE_ATTRIBUTES = {
'data-status': 'active',
'aria-current': 'page',
}
const STATIC_TRANSITIONING_ATTRIBUTES = {
'data-transitioning': 'transitioning',
}

/** Call a JSX.EventHandlerUnion with the event. */
function callHandler<T, TEvent extends Event>(
Expand DownExpand Up@@ -569,10 +557,7 @@ export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Solid.JSX.Element
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Solid.JSX.Element)
| ((state: { isActive: boolean }) => Solid.JSX.Element)
}

type LinkComponentSolidProps<TComp> = TComp extends Solid.ValidComponent
Expand DownExpand Up@@ -655,9 +640,6 @@ export const Link: LinkComponent<'a'> = (props) => {
get isActive() {
return (linkProps as any)['data-status'] === 'active'
},
get isTransitioning() {
return (linkProps as any)['data-transitioning'] === 'transitioning'
},
})
}

Expand Down
32 changes: 2 additions & 30 deletions packages/vue-router/src/link.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -86,7 +86,6 @@ export function useLinkProps<
options: UseLinkPropsOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>,
): LinkHTMLAttributes {
const router = useRouter()
const isTransitioning = Vue.ref(false)
let hasRenderFetched = false

// Ensure router is defined before proceeding
Expand DownExpand Up@@ -200,7 +199,6 @@ export function useLinkProps<
href,
options: options as AnyLinkPropsOptions,
isActive,
isTransitioning: false,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -299,13 +297,6 @@ export function useLinkProps<

e.preventDefault()

isTransitioning.value = true

const unsub = router.subscribe('onResolved', () => {
unsub()
isTransitioning.value = false
})

// All is well? Navigate!
router.navigate({
...options,
Expand DownExpand Up@@ -423,7 +414,6 @@ export function useLinkProps<
ref,
staticEventHandlers,
isActive: isActive.value,
isTransitioning: isTransitioning.value,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand DownExpand Up@@ -486,7 +476,6 @@ function combineResultProps({
href,
options,
isActive,
isTransitioning,
resolvedActiveProps,
resolvedInactiveProps,
resolvedClassName,
Expand All@@ -498,7 +487,6 @@ function combineResultProps({
href: string | undefined
options: AnyLinkPropsOptions
isActive: boolean
isTransitioning: boolean
resolvedActiveProps: StyledProps
resolvedInactiveProps: StyledProps
resolvedClassName?: string
Expand DownExpand Up@@ -542,10 +530,6 @@ function combineResultProps({
result['aria-current'] = 'page'
}

if (isTransitioning) {
result['data-transitioning'] = 'transitioning'
}

for (const key of Object.keys(resolvedActiveProps)) {
if (key !== 'class' && key !== 'style') {
result[key] = resolvedActiveProps[key]
Expand DownExpand Up@@ -753,12 +737,7 @@ export type LinkProps<

export interface LinkPropsChildren {
// If a function is passed as a child, it will be given the `isActive` boolean to aid in further styling on the element it returns
children?:
| Vue.VNodeChild
| ((state: {
isActive: boolean
isTransitioning: boolean
}) => Vue.VNodeChild)
children?: Vue.VNodeChild | ((state: { isActive: boolean }) => Vue.VNodeChild)
}

type LinkComponentVueProps<TComp> = TComp extends keyof HTMLElementTagNameMap
Expand DownExpand Up@@ -871,16 +850,9 @@ const LinkImpl = Vue.defineComponent({
const linkProps = Vue.unref(linkPropsSource)

const isActive = linkProps['data-status'] === 'active'
const isTransitioning =
linkProps['data-transitioning'] === 'transitioning'

// Create the slot content or empty array if no default slot
const slotContent = slots.default
? slots.default({
isActive,
isTransitioning,
})
: []
const slotContent = slots.default ? slots.default({ isActive }) : []

// Special handling for SVG links - wrap an <a> inside the SVG
if (Component === 'svg') {
Expand Down
73 changes: 0 additions & 73 deletions packages/vue-router/tests/link.test.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -5318,79 +5318,6 @@ describe('Link', () => {
expect(window.location.pathname).toBe('/posts')
})

test('Link slot receives isTransitioning during pending navigation', async () => {
let resolvePostsLoader: (() => void) | undefined

const postsLoaderPromise = new Promise<void>((resolve) => {
resolvePostsLoader = resolve
})

const rootRoute = createRootRoute({
component: () =>
Vue.h(Vue.Fragment, null, [
Vue.h(
Link,
{ to: '/posts', 'data-testid': 'posts-link' },
{
default: ({ isTransitioning }: { isTransitioning: boolean }) => (
<>
<span data-testid="slot-transition-state">
{isTransitioning ? 'transitioning' : 'idle'}
</span>
<span>Posts</span>
</>
),
},
),
Vue.h(Outlet),
]),
})

const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <h1>Index page</h1>,
})

const postsRoute = createRoute({
ssr: false,
getParentRoute: () => rootRoute,
path: '/posts',
loader: () => postsLoaderPromise,
component: () => <h1>Posts page</h1>,
})

const router = createRouter({
routeTree: rootRoute.addChildren([indexRoute, postsRoute]),
history,
})

render(<RouterProvider router={router} />)

await screen.findByRole('heading', { name: 'Index page' })

const postsLink = await screen.findByTestId('posts-link')
const transitionState = await screen.findByTestId('slot-transition-state')

expect(transitionState).toHaveTextContent('idle')
expect(postsLink).not.toHaveAttribute('data-transitioning')

fireEvent.click(postsLink)

await waitFor(() => expect(resolvePostsLoader).toBeDefined())
await waitFor(() =>
expect(transitionState).toHaveTextContent('transitioning'),
)
expect(postsLink).toHaveAttribute('data-transitioning', 'transitioning')

resolvePostsLoader?.()

await screen.findByRole('heading', { name: 'Posts page' })

await waitFor(() => expect(transitionState).toHaveTextContent('idle'))
expect(postsLink).not.toHaveAttribute('data-transitioning')
})

describe('when preloading a link, `preload` should be', () => {
async function runTest({
expectedPreload,
Expand Down
Loading