@buzola/router 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/define/create-page.d.ts +7 -6
  2. package/dist/define/create-page.d.ts.map +1 -1
  3. package/dist/define/create-page.js +33 -120
  4. package/dist/define/create-page.js.map +1 -1
  5. package/dist/engine/loader-state.d.ts +68 -0
  6. package/dist/engine/loader-state.d.ts.map +1 -0
  7. package/dist/engine/loader-state.js +135 -0
  8. package/dist/engine/loader-state.js.map +1 -0
  9. package/dist/engine/route-tree.d.ts +1 -0
  10. package/dist/engine/route-tree.d.ts.map +1 -1
  11. package/dist/engine/route-tree.js +3 -1
  12. package/dist/engine/route-tree.js.map +1 -1
  13. package/dist/engine/router.d.ts +6 -0
  14. package/dist/engine/router.d.ts.map +1 -1
  15. package/dist/engine/router.js +19 -1
  16. package/dist/engine/router.js.map +1 -1
  17. package/dist/engine/types.d.ts +6 -0
  18. package/dist/engine/types.d.ts.map +1 -1
  19. package/dist/react/hooks.d.ts +2 -0
  20. package/dist/react/hooks.d.ts.map +1 -1
  21. package/dist/react/hooks.js +5 -1
  22. package/dist/react/hooks.js.map +1 -1
  23. package/dist/react/link.d.ts +7 -0
  24. package/dist/react/link.d.ts.map +1 -1
  25. package/dist/react/link.js +33 -6
  26. package/dist/react/link.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/__tests__/loader-state.test.ts +339 -0
  29. package/src/__tests__/router.test.ts +58 -0
  30. package/src/__tests__/type-safety.test.tsx +24 -0
  31. package/src/define/create-page.tsx +49 -140
  32. package/src/engine/loader-state.ts +178 -0
  33. package/src/engine/route-tree.ts +4 -0
  34. package/src/engine/router.ts +19 -1
  35. package/src/engine/types.ts +6 -0
  36. package/src/react/hooks.ts +5 -1
  37. package/src/react/link.tsx +49 -5
@@ -1,4 +1,4 @@
1
- import React, { useCallback } from 'react'
1
+ import React, { useCallback, useEffect, useRef } from 'react'
2
2
  import type { EffectivePageParams, NavigateOptions, RegisteredPage } from '../engine/types.js'
3
3
  import { useRouter, useRouterState } from './hooks.js'
4
4
 
@@ -27,6 +27,13 @@ type LinkPropsBase = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'>
27
27
  * When `true`, clones the single child element and merges `href`, `onClick`, and `data-active` props into it.
28
28
  */
29
29
  asChild?: boolean
30
+ /**
31
+ * Prefetch strategy for lazy-loaded route code.
32
+ * - `'intent'` — preload on mouseEnter/focus (default)
33
+ * - `'render'` — preload as soon as the Link renders
34
+ * - `'none'` — disable prefetching
35
+ */
36
+ prefetch?: 'intent' | 'render' | 'none'
30
37
  }
31
38
 
32
39
  export type LinkProps<P extends RegisteredPage = RegisteredPage> = [keyof EffectivePageParams<P>] extends [never]
@@ -53,13 +60,17 @@ export function Link<P extends RegisteredPage>(props: LinkProps<P>): React.React
53
60
  resetScroll,
54
61
  activeExact = true,
55
62
  asChild,
63
+ prefetch = 'intent',
56
64
  onClick,
65
+ onMouseEnter,
66
+ onFocus,
57
67
  children,
58
68
  ...rest
59
69
  } = props as LinkPropsBase & { to: string; params?: Record<string, string> }
60
70
 
61
71
  const router = useRouter()
62
72
  const routerState = useRouterState()
73
+ const preloadedRef = useRef(false)
63
74
 
64
75
  const href = router.buildPagePath(to, params)
65
76
  const needsProgrammaticNav = state !== undefined || replace || viewTransition || resetScroll === false
@@ -71,6 +82,33 @@ export function Link<P extends RegisteredPage>(props: LinkProps<P>): React.React
71
82
  const isPrefix = targetPath !== '/' && currentPath.startsWith(targetPath + '/')
72
83
  const isActive = isExact || (!activeExact && isPrefix)
73
84
 
85
+ const doPreload = useCallback(() => {
86
+ if (preloadedRef.current) return
87
+ preloadedRef.current = true
88
+ router.preload(to, params)
89
+ }, [router, to, params])
90
+
91
+ // Prefetch on render
92
+ useEffect(() => {
93
+ if (prefetch === 'render') doPreload()
94
+ }, [prefetch, doPreload])
95
+
96
+ const handleMouseEnter = useCallback(
97
+ (e: React.MouseEvent<HTMLAnchorElement>) => {
98
+ onMouseEnter?.(e)
99
+ if (prefetch === 'intent') doPreload()
100
+ },
101
+ [onMouseEnter, prefetch, doPreload],
102
+ )
103
+
104
+ const handleFocus = useCallback(
105
+ (e: React.FocusEvent<HTMLAnchorElement>) => {
106
+ onFocus?.(e)
107
+ if (prefetch === 'intent') doPreload()
108
+ },
109
+ [onFocus, prefetch, doPreload],
110
+ )
111
+
74
112
  const handleClick = useCallback(
75
113
  (e: React.MouseEvent<HTMLAnchorElement>) => {
76
114
  onClick?.(e)
@@ -91,18 +129,24 @@ export function Link<P extends RegisteredPage>(props: LinkProps<P>): React.React
91
129
  [onClick, needsProgrammaticNav, router, to, params, replace, state, viewTransition, resetScroll],
92
130
  )
93
131
 
132
+ const commonProps = {
133
+ href,
134
+ onClick: handleClick,
135
+ onMouseEnter: handleMouseEnter,
136
+ onFocus: handleFocus,
137
+ 'data-active': isActive ? '' : undefined,
138
+ }
139
+
94
140
  if (asChild) {
95
141
  const child = React.Children.only(children) as React.ReactElement<any>
96
142
  return React.cloneElement(child, {
97
- href,
98
- onClick: handleClick,
99
- 'data-active': isActive ? '' : undefined,
143
+ ...commonProps,
100
144
  ...rest,
101
145
  })
102
146
  }
103
147
 
104
148
  return (
105
- <a href={href} onClick={handleClick} data-active={isActive ? '' : undefined} {...rest}>
149
+ <a {...commonProps} {...rest}>
106
150
  {children}
107
151
  </a>
108
152
  )