@cmj/juice 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/client/Link.d.ts +74 -0
  2. package/dist/client/Link.d.ts.map +1 -0
  3. package/dist/client/Link.js +132 -0
  4. package/dist/client/Link.js.map +1 -0
  5. package/dist/client/client.d.ts +1 -1
  6. package/dist/client/client.d.ts.map +1 -1
  7. package/dist/client/client.js +6 -34
  8. package/dist/client/client.js.map +1 -1
  9. package/dist/client/hooks.d.ts +85 -0
  10. package/dist/client/hooks.d.ts.map +1 -0
  11. package/dist/client/hooks.js +82 -0
  12. package/dist/client/hooks.js.map +1 -0
  13. package/dist/client/index.d.ts +3 -1
  14. package/dist/client/index.d.ts.map +1 -1
  15. package/dist/client/index.js +3 -1
  16. package/dist/client/index.js.map +1 -1
  17. package/dist/client/nav.d.ts +40 -0
  18. package/dist/client/nav.d.ts.map +1 -0
  19. package/dist/client/nav.js +85 -0
  20. package/dist/client/nav.js.map +1 -0
  21. package/dist/compiler/dts.d.ts +33 -0
  22. package/dist/compiler/dts.d.ts.map +1 -0
  23. package/dist/compiler/dts.js +102 -0
  24. package/dist/compiler/dts.js.map +1 -0
  25. package/dist/compiler/plugin.d.ts.map +1 -1
  26. package/dist/compiler/plugin.js +42 -1
  27. package/dist/compiler/plugin.js.map +1 -1
  28. package/dist/runtime/actions.d.ts +6 -5
  29. package/dist/runtime/actions.d.ts.map +1 -1
  30. package/dist/runtime/actions.js +17 -7
  31. package/dist/runtime/actions.js.map +1 -1
  32. package/dist/runtime/index.d.ts +1 -1
  33. package/dist/runtime/index.d.ts.map +1 -1
  34. package/dist/runtime/matcher.d.ts.map +1 -1
  35. package/dist/runtime/matcher.js +39 -13
  36. package/dist/runtime/matcher.js.map +1 -1
  37. package/dist/runtime/router.js +1 -1
  38. package/dist/runtime/router.js.map +1 -1
  39. package/dist/runtime/types.d.ts +48 -11
  40. package/dist/runtime/types.d.ts.map +1 -1
  41. package/package.json +2 -2
@@ -0,0 +1,74 @@
1
+ import { type AnchorHTMLAttributes, type ReactNode, type CSSProperties, type Ref } from 'react';
2
+ /**
3
+ * Prefetch strategy for the `<Link>` component.
4
+ *
5
+ * - `'hover'` — Prefetch when the user hovers or focuses the link. *(default)*
6
+ * - `'viewport'` — Prefetch when the link scrolls into the viewport.
7
+ * - `'none'` — No automatic prefetching.
8
+ */
9
+ export type PrefetchStrategy = 'hover' | 'viewport' | 'none';
10
+ /**
11
+ * Props for the `<Link>` component.
12
+ *
13
+ * Extends all standard `<a>` HTML attributes so you can pass
14
+ * `target`, `rel`, `aria-*`, `data-*`, etc. directly.
15
+ */
16
+ export interface LinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
17
+ /** The URL to navigate to. Required. */
18
+ href: string;
19
+ /**
20
+ * Prefetch strategy.
21
+ * @default 'hover'
22
+ */
23
+ prefetch?: PrefetchStrategy;
24
+ /**
25
+ * Replace the current history entry instead of pushing a new one.
26
+ * @default false
27
+ */
28
+ replace?: boolean;
29
+ /**
30
+ * Scroll to the top after navigation.
31
+ * @default true
32
+ */
33
+ scroll?: boolean;
34
+ /**
35
+ * CSS class name applied when the link's `href` matches the
36
+ * current pathname.
37
+ */
38
+ activeClassName?: string;
39
+ /**
40
+ * Inline styles applied when the link's `href` matches the
41
+ * current pathname.
42
+ */
43
+ activeStyle?: CSSProperties;
44
+ /** React ref forwarded to the underlying `<a>` element. */
45
+ ref?: Ref<HTMLAnchorElement>;
46
+ children?: ReactNode;
47
+ }
48
+ /**
49
+ * Client-side navigation link for Juice SPA apps.
50
+ *
51
+ * Renders a standard `<a>` tag that works with or without JavaScript.
52
+ * When the Navigation API is available and `initNavigation()` has been
53
+ * called, clicking this link fetches an RSC payload instead of causing
54
+ * a full page reload.
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * import { Link } from '@cmj/juice/client';
59
+ *
60
+ * // Basic usage
61
+ * <Link href="/about">About</Link>
62
+ *
63
+ * // Viewport prefetching
64
+ * <Link href="/products" prefetch="viewport">Products</Link>
65
+ *
66
+ * // Active link styling
67
+ * <Link href="/blog" activeClassName="nav-active">Blog</Link>
68
+ *
69
+ * // Replace history entry
70
+ * <Link href="/login" replace>Log in</Link>
71
+ * ```
72
+ */
73
+ export declare function Link(props: LinkProps): ReactNode;
74
+ //# sourceMappingURL=Link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Link.d.ts","sourceRoot":"","sources":["../../src/client/Link.ts"],"names":[],"mappings":"AAOA,OAAO,EAKL,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,GAAG,EACT,MAAM,OAAO,CAAC;AAYf;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,SAAU,SAAQ,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACtF,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAE5B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC;IAE5B,2DAA2D;IAC3D,GAAG,CAAC,EAAE,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAE7B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAmBD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAmHhD"}
@@ -0,0 +1,132 @@
1
+ // ── juice/client — <Link> component ───────────────────────────────
2
+ // Declarative SPA navigation for Juice. Renders a standard `<a>` tag
3
+ // with built-in RSC prefetching and active-state detection.
4
+ //
5
+ // Progressive enhancement: works as a normal link without JS.
6
+ // ──────────────────────────────────────────────────────────────────
7
+ import { createElement, useRef, useEffect, useSyncExternalStore, } from 'react';
8
+ import { prefetchRSC, isSameOrigin, _subscribeToPathname, _getPathnameSnapshot, _getServerPathnameSnapshot, } from './nav.js';
9
+ // ── Helpers ───────────────────────────────────────────────────────
10
+ /**
11
+ * Check if a pathname matches the link's href.
12
+ * Exact match — `/about` matches `/about`, not `/about/team`.
13
+ */
14
+ function isActive(href, pathname) {
15
+ try {
16
+ const url = new URL(href, 'http://n');
17
+ return url.pathname === pathname;
18
+ }
19
+ catch {
20
+ return href === pathname;
21
+ }
22
+ }
23
+ // ── Component ─────────────────────────────────────────────────────
24
+ /**
25
+ * Client-side navigation link for Juice SPA apps.
26
+ *
27
+ * Renders a standard `<a>` tag that works with or without JavaScript.
28
+ * When the Navigation API is available and `initNavigation()` has been
29
+ * called, clicking this link fetches an RSC payload instead of causing
30
+ * a full page reload.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * import { Link } from '@cmj/juice/client';
35
+ *
36
+ * // Basic usage
37
+ * <Link href="/about">About</Link>
38
+ *
39
+ * // Viewport prefetching
40
+ * <Link href="/products" prefetch="viewport">Products</Link>
41
+ *
42
+ * // Active link styling
43
+ * <Link href="/blog" activeClassName="nav-active">Blog</Link>
44
+ *
45
+ * // Replace history entry
46
+ * <Link href="/login" replace>Log in</Link>
47
+ * ```
48
+ */
49
+ export function Link(props) {
50
+ const { href, prefetch = 'hover', replace = false, scroll, activeClassName, activeStyle, className, style, children, onPointerEnter, onFocus, ref: externalRef, ...rest } = props;
51
+ const internalRef = useRef(null);
52
+ // Merge refs
53
+ const setRef = (el) => {
54
+ internalRef.current = el;
55
+ if (typeof externalRef === 'function') {
56
+ externalRef(el);
57
+ }
58
+ else if (externalRef && typeof externalRef === 'object') {
59
+ externalRef.current = el;
60
+ }
61
+ };
62
+ // ── Active state ──────────────────────────────────────────────
63
+ const pathname = useSyncExternalStore(_subscribeToPathname, _getPathnameSnapshot, _getServerPathnameSnapshot);
64
+ const active = isActive(href, pathname);
65
+ // ── Viewport prefetching ──────────────────────────────────────
66
+ useEffect(() => {
67
+ if (prefetch !== 'viewport')
68
+ return;
69
+ const el = internalRef.current;
70
+ if (!el)
71
+ return;
72
+ if (!('IntersectionObserver' in globalThis))
73
+ return;
74
+ const observer = new IntersectionObserver((entries) => {
75
+ for (const entry of entries) {
76
+ if (entry.isIntersecting) {
77
+ const resolvedUrl = new URL(href, location.href).href;
78
+ prefetchRSC(resolvedUrl);
79
+ observer.unobserve(entry.target);
80
+ }
81
+ }
82
+ }, { rootMargin: '200px' });
83
+ observer.observe(el);
84
+ return () => observer.disconnect();
85
+ }, [href, prefetch]);
86
+ // ── Hover/focus prefetching ───────────────────────────────────
87
+ const handlePointerEnter = (e) => {
88
+ if (prefetch === 'hover' && isSameOrigin(href)) {
89
+ prefetchRSC(new URL(href, location.href).href);
90
+ }
91
+ onPointerEnter?.(e);
92
+ };
93
+ const handleFocus = (e) => {
94
+ if (prefetch === 'hover' && isSameOrigin(href)) {
95
+ prefetchRSC(new URL(href, location.href).href);
96
+ }
97
+ onFocus?.(e);
98
+ };
99
+ // ── Merge class names ─────────────────────────────────────────
100
+ const resolvedClassName = active && activeClassName
101
+ ? className
102
+ ? `${className} ${activeClassName}`
103
+ : activeClassName
104
+ : className;
105
+ // ── Merge styles ──────────────────────────────────────────────
106
+ const resolvedStyle = active && activeStyle
107
+ ? style
108
+ ? { ...style, ...activeStyle }
109
+ : activeStyle
110
+ : style;
111
+ // ── Build anchor props ────────────────────────────────────────
112
+ const anchorProps = {
113
+ ...rest,
114
+ href,
115
+ ref: setRef,
116
+ className: resolvedClassName,
117
+ style: resolvedStyle,
118
+ onPointerEnter: handlePointerEnter,
119
+ onFocus: handleFocus,
120
+ 'data-active': active || undefined,
121
+ };
122
+ // Signal prefetch strategy to the DOM for IntersectionObserver
123
+ if (prefetch === 'viewport') {
124
+ anchorProps['data-prefetch'] = '';
125
+ }
126
+ // Signal replace intent
127
+ if (replace) {
128
+ anchorProps['data-replace'] = '';
129
+ }
130
+ return createElement('a', anchorProps, children);
131
+ }
132
+ //# sourceMappingURL=Link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Link.js","sourceRoot":"","sources":["../../src/client/Link.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
+ export { RSC_CONTENT_TYPE, prefetchRSC, isSameOrigin, _subscribeToPathname, _getPathnameSnapshot, _getServerPathnameSnapshot, } from './nav.js';
2
3
  /**
3
4
  * Callback to update the React root with a new RSC payload.
4
5
  * Set by `initNavigation()`.
@@ -53,5 +54,4 @@ export interface NavigationOptions {
53
54
  * ```
54
55
  */
55
56
  export declare function initNavigation(setPage: PageSetter, options?: NavigationOptions): () => void;
56
- export {};
57
57
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AASA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AASxD;;;GAGG;AACH,KAAK,UAAU,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAoGD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,UAAU,EACnB,OAAO,GAAE,iBAAsB,GAC9B,MAAM,IAAI,CAmFZ"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"AASA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAWxD,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,UAAU,CAAC;AAElB;;;GAGG;AACH,KAAK,UAAU,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;AAE5C;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAChE;AAuED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,UAAU,EACnB,OAAO,GAAE,iBAAsB,GAC9B,MAAM,IAAI,CA+EZ"}
@@ -8,24 +8,9 @@
8
8
  // ────────────────────────────────────────────────────────────────────
9
9
  import { startTransition } from 'react';
10
10
  import { createFromFetch } from 'react-server-dom-webpack/client.browser';
11
- /**
12
- * The RSC content type used for payload negotiation.
13
- * Must match the server's `RSC_CONTENT_TYPE` in `rsc.ts`.
14
- */
15
- const RSC_CONTENT_TYPE = 'text/x-component';
16
- // ── Prefetch cache ────────────────────────────────────────────────
17
- const prefetchCache = new Map();
18
- function prefetchRSC(url) {
19
- if (prefetchCache.has(url))
20
- return;
21
- const promise = fetch(url, {
22
- headers: { 'Accept': RSC_CONTENT_TYPE },
23
- priority: 'low',
24
- });
25
- prefetchCache.set(url, promise);
26
- // Expire after 30 seconds to avoid stale data
27
- setTimeout(() => prefetchCache.delete(url), 30_000);
28
- }
11
+ import { RSC_CONTENT_TYPE, prefetchRSC, _consumePrefetch, isSameOrigin, } from './nav.js';
12
+ // Re-export shared utilities for backward compatibility
13
+ export { RSC_CONTENT_TYPE, prefetchRSC, isSameOrigin, _subscribeToPathname, _getPathnameSnapshot, _getServerPathnameSnapshot, } from './nav.js';
29
14
  // ── Prefetch observer ─────────────────────────────────────────────
30
15
  function setupPrefetching() {
31
16
  // Prefetch on hover (desktop) and focus (keyboard nav)
@@ -80,16 +65,6 @@ function setupPrefetching() {
80
65
  }).observe(document.body, { childList: true, subtree: true });
81
66
  }
82
67
  }
83
- // ── Helpers ───────────────────────────────────────────────────────
84
- function isSameOrigin(href) {
85
- try {
86
- const url = new URL(href, location.href);
87
- return url.origin === location.origin;
88
- }
89
- catch {
90
- return false;
91
- }
92
- }
93
68
  // ── View Transitions wrapper ──────────────────────────────────────
94
69
  function withViewTransition(enabled, fn) {
95
70
  if (enabled && 'startViewTransition' in document) {
@@ -166,15 +141,12 @@ export function initNavigation(setPage, options = {}) {
166
141
  scroll: 'after-transition',
167
142
  async handler() {
168
143
  const url = event.destination.url;
169
- // Use prefetched response if available
170
- const fetchPromise = prefetchCache.has(url)
171
- ? prefetchCache.get(url)
172
- : fetch(url, {
144
+ // Use prefetched response if available, otherwise fetch
145
+ const fetchPromise = _consumePrefetch(url)
146
+ ?? fetch(url, {
173
147
  headers: { 'Accept': RSC_CONTENT_TYPE },
174
148
  signal: event.signal,
175
149
  });
176
- // Clear the prefetch cache entry
177
- prefetchCache.delete(url);
178
150
  // Decode the RSC payload
179
151
  const rscPayload = createFromFetch(fetchPromise, {
180
152
  callServer,
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/client/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Return type of `useRouter()`.
3
+ */
4
+ export interface Router {
5
+ /** The current pathname (reactive — re-renders on navigation). */
6
+ pathname: string;
7
+ /**
8
+ * Navigate to a URL, pushing a new history entry.
9
+ *
10
+ * @param url - The URL to navigate to.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const router = useRouter();
15
+ * router.push('/product/42');
16
+ * ```
17
+ */
18
+ push: (url: string) => void;
19
+ /**
20
+ * Navigate to a URL, replacing the current history entry.
21
+ *
22
+ * @param url - The URL to navigate to.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const router = useRouter();
27
+ * router.replace('/login');
28
+ * ```
29
+ */
30
+ replace: (url: string) => void;
31
+ /**
32
+ * Prefetch an RSC payload for a URL. Idempotent — duplicate
33
+ * calls for the same URL are no-ops until the cache expires.
34
+ *
35
+ * @param url - The URL to prefetch.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const router = useRouter();
40
+ * router.prefetch('/dashboard');
41
+ * ```
42
+ */
43
+ prefetch: (url: string) => void;
44
+ /**
45
+ * Go back in history. Equivalent to `history.back()`.
46
+ */
47
+ back: () => void;
48
+ /**
49
+ * Go forward in history. Equivalent to `history.forward()`.
50
+ */
51
+ forward: () => void;
52
+ }
53
+ /**
54
+ * Programmatic navigation hook for Juice SPA apps.
55
+ *
56
+ * Returns a stable `Router` object with `push`, `replace`, `prefetch`,
57
+ * `back`, `forward`, and a reactive `pathname`.
58
+ *
59
+ * Uses the Navigation API when available. Falls back to `location.assign`
60
+ * when the Navigation API is absent (MPA mode — full page reload).
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * 'use client';
65
+ * import { useRouter } from '@cmj/juice/client';
66
+ *
67
+ * function SearchBar() {
68
+ * const router = useRouter();
69
+ *
70
+ * function handleSubmit(e: FormEvent) {
71
+ * e.preventDefault();
72
+ * const q = new FormData(e.currentTarget).get('q');
73
+ * router.push(`/search?q=${encodeURIComponent(q)}`);
74
+ * }
75
+ *
76
+ * return (
77
+ * <form onSubmit={handleSubmit}>
78
+ * <input name="q" />
79
+ * </form>
80
+ * );
81
+ * }
82
+ * ```
83
+ */
84
+ export declare function useRouter(): Router;
85
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/client/hooks.ts"],"names":[],"mappings":"AAmBA;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;;;;;OAUG;IACH,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAE5B;;;;;;;;;;OAUG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAE/B;;;;;;;;;;;OAWG;IACH,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhC;;OAEG;IACH,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAgDlC"}
@@ -0,0 +1,82 @@
1
+ // ── juice/client — useRouter hook ─────────────────────────────────
2
+ // Programmatic navigation for Juice SPA apps. Uses the Navigation API
3
+ // under the hood — the existing `initNavigation()` interceptor handles
4
+ // RSC payload fetching automatically.
5
+ //
6
+ // ~500 bytes minified. Zero dependencies beyond React 19.
7
+ // ──────────────────────────────────────────────────────────────────
8
+ import { useSyncExternalStore, useCallback, useMemo } from 'react';
9
+ import { prefetchRSC, _subscribeToPathname, _getPathnameSnapshot, _getServerPathnameSnapshot, } from './nav.js';
10
+ // ── Hook ──────────────────────────────────────────────────────────
11
+ /**
12
+ * Programmatic navigation hook for Juice SPA apps.
13
+ *
14
+ * Returns a stable `Router` object with `push`, `replace`, `prefetch`,
15
+ * `back`, `forward`, and a reactive `pathname`.
16
+ *
17
+ * Uses the Navigation API when available. Falls back to `location.assign`
18
+ * when the Navigation API is absent (MPA mode — full page reload).
19
+ *
20
+ * @example
21
+ * ```tsx
22
+ * 'use client';
23
+ * import { useRouter } from '@cmj/juice/client';
24
+ *
25
+ * function SearchBar() {
26
+ * const router = useRouter();
27
+ *
28
+ * function handleSubmit(e: FormEvent) {
29
+ * e.preventDefault();
30
+ * const q = new FormData(e.currentTarget).get('q');
31
+ * router.push(`/search?q=${encodeURIComponent(q)}`);
32
+ * }
33
+ *
34
+ * return (
35
+ * <form onSubmit={handleSubmit}>
36
+ * <input name="q" />
37
+ * </form>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ export function useRouter() {
43
+ const pathname = useSyncExternalStore(_subscribeToPathname, _getPathnameSnapshot, _getServerPathnameSnapshot);
44
+ const push = useCallback((url) => {
45
+ if ('navigation' in globalThis) {
46
+ globalThis.navigation.navigate(url, { history: 'push' });
47
+ }
48
+ else {
49
+ globalThis.location.assign(url);
50
+ }
51
+ }, []);
52
+ const replace = useCallback((url) => {
53
+ if ('navigation' in globalThis) {
54
+ globalThis.navigation.navigate(url, { history: 'replace' });
55
+ }
56
+ else {
57
+ globalThis.location.replace(url);
58
+ }
59
+ }, []);
60
+ const prefetch = useCallback((url) => {
61
+ const resolved = new URL(url, globalThis.location?.href ?? 'http://localhost').href;
62
+ prefetchRSC(resolved);
63
+ }, []);
64
+ const back = useCallback(() => {
65
+ if ('navigation' in globalThis) {
66
+ globalThis.navigation.back();
67
+ }
68
+ else {
69
+ globalThis.history?.back();
70
+ }
71
+ }, []);
72
+ const forward = useCallback(() => {
73
+ if ('navigation' in globalThis) {
74
+ globalThis.navigation.forward();
75
+ }
76
+ else {
77
+ globalThis.history?.forward();
78
+ }
79
+ }, []);
80
+ return useMemo(() => ({ pathname, push, replace, prefetch, back, forward }), [pathname, push, replace, prefetch, back, forward]);
81
+ }
82
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../src/client/hooks.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,sEAAsE;AACtE,uEAAuE;AACvE,sCAAsC;AACtC,EAAE;AACF,0DAA0D;AAC1D,qEAAqE;AAErE,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEnE,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,UAAU,CAAC;AA8DlB,qEAAqE;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,QAAQ,GAAG,oBAAoB,CACnC,oBAAoB,EACpB,oBAAoB,EACpB,0BAA0B,CAC3B,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,GAAW,EAAE,EAAE;QACvC,IAAI,YAAY,IAAI,UAAU,EAAE,CAAC;YAC9B,UAAkB,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;QACpE,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,GAAW,EAAE,EAAE;QAC1C,IAAI,YAAY,IAAI,UAAU,EAAE,CAAC;YAC9B,UAAkB,CAAC,UAAU,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,GAAW,EAAE,EAAE;QAC3C,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,QAAQ,EAAE,IAAI,IAAI,kBAAkB,CAAC,CAAC,IAAI,CAAC;QACpF,WAAW,CAAC,QAAQ,CAAC,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,IAAI,YAAY,IAAI,UAAU,EAAE,CAAC;YAC9B,UAAkB,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;QACxC,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;QAC7B,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE;QAC/B,IAAI,YAAY,IAAI,UAAU,EAAE,CAAC;YAC9B,UAAkB,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;QAChC,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAC5D,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,CACnD,CAAC;AACJ,CAAC"}
@@ -1,2 +1,4 @@
1
- export { initNavigation, type NavigationOptions } from './client.js';
1
+ export { initNavigation, prefetchRSC, type NavigationOptions } from './client.js';
2
+ export { Link, type LinkProps, type PrefetchStrategy } from './Link.js';
3
+ export { useRouter, type Router } from './hooks.js';
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,IAAI,EAAE,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC"}
@@ -1,3 +1,5 @@
1
1
  // ── juice/client barrel export ────────────────────────────────────
2
- export { initNavigation } from './client.js';
2
+ export { initNavigation, prefetchRSC } from './client.js';
3
+ export { Link } from './Link.js';
4
+ export { useRouter } from './hooks.js';
3
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,cAAc,EAA0B,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,cAAc,EAAE,WAAW,EAA0B,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,IAAI,EAAyC,MAAM,WAAW,CAAC;AACxE,OAAO,EAAE,SAAS,EAAe,MAAM,YAAY,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The RSC content type used for payload negotiation.
3
+ * Must match the server's `RSC_CONTENT_TYPE` in `rsc.ts`.
4
+ */
5
+ export declare const RSC_CONTENT_TYPE = "text/x-component";
6
+ /**
7
+ * Prefetch an RSC payload for a given URL. Idempotent — duplicate
8
+ * calls for the same URL are no-ops until the cache entry expires.
9
+ *
10
+ * Used internally by `initNavigation()` and exported for use by
11
+ * `<Link prefetch="hover">` and `useRouter().prefetch()`.
12
+ */
13
+ export declare function prefetchRSC(url: string): void;
14
+ /**
15
+ * Get a prefetched response from the cache, if available.
16
+ * Removes the entry from cache (one-time read).
17
+ * @internal
18
+ */
19
+ export declare function _consumePrefetch(url: string): Promise<Response> | undefined;
20
+ /** @internal */
21
+ export declare function isSameOrigin(href: string): boolean;
22
+ /**
23
+ * Subscribe to pathname changes via the Navigation API.
24
+ * Returns an unsubscribe function.
25
+ *
26
+ * For use with React's `useSyncExternalStore`.
27
+ * @internal
28
+ */
29
+ export declare function _subscribeToPathname(callback: () => void): () => void;
30
+ /**
31
+ * Get the current pathname snapshot for `useSyncExternalStore`.
32
+ * @internal
33
+ */
34
+ export declare function _getPathnameSnapshot(): string;
35
+ /**
36
+ * Server snapshot for SSR — always returns '/'.
37
+ * @internal
38
+ */
39
+ export declare function _getServerPathnameSnapshot(): string;
40
+ //# sourceMappingURL=nav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nav.d.ts","sourceRoot":"","sources":["../../src/client/nav.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAMnD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAY7C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,SAAS,CAM3E;AAID,gBAAgB;AAChB,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOlD;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAUrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAEnD"}