@chrryai/pepper 1.1.78

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.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # 🌶️ Pepper Router
2
+
3
+ **Universal router for React** - Works seamlessly in web apps, React Native, and browser extensions with built-in view transitions.
4
+
5
+ ## ✨ Features
6
+
7
+ - 🌍 **Universal** - One API for web, React Native, and browser extensions
8
+ - ⚡ **Lightweight** - Zero dependencies, minimal bundle size
9
+ - 🎨 **View Transitions** - Built-in support for smooth page transitions
10
+ - 📱 **Platform-aware** - Automatically adapts to your environment
11
+ - 🔒 **Type-safe** - Full TypeScript support
12
+ - 🚀 **Battle-tested** - Powers [Vex](https://askvex.com)'s polished UX
13
+
14
+ ## 📦 Installation
15
+
16
+ ```bash
17
+ npm install @askvex/pepper
18
+ ```
19
+
20
+ ## 🚀 Quick Start
21
+
22
+ ### Web (with View Transitions)
23
+
24
+ ```tsx
25
+ import { HistoryRouterProvider, useNavigation } from "@askvex/pepper/web"
26
+
27
+ function App() {
28
+ return (
29
+ <HistoryRouterProvider>
30
+ <YourApp />
31
+ </HistoryRouterProvider>
32
+ )
33
+ }
34
+
35
+ function YourComponent() {
36
+ const { navigate, pathname } = useNavigation()
37
+
38
+ return <button onClick={() => navigate("/about")}>Go to About</button>
39
+ }
40
+ ```
41
+
42
+ ### React Native
43
+
44
+ ```tsx
45
+ import { HistoryRouterProvider, useNavigation } from "@askvex/pepper/native"
46
+
47
+ function App() {
48
+ return (
49
+ <HistoryRouterProvider>
50
+ <YourApp />
51
+ </HistoryRouterProvider>
52
+ )
53
+ }
54
+ ```
55
+
56
+ ### Browser Extension
57
+
58
+ ```tsx
59
+ import { HistoryRouterProvider, useNavigation } from "@askvex/pepper/extension"
60
+
61
+ function App() {
62
+ return (
63
+ <HistoryRouterProvider>
64
+ <YourApp />
65
+ </HistoryRouterProvider>
66
+ )
67
+ }
68
+ ```
69
+
70
+ ## 📖 API
71
+
72
+ ### `HistoryRouterProvider`
73
+
74
+ Wrap your app with this provider to enable routing.
75
+
76
+ ```tsx
77
+ <HistoryRouterProvider>
78
+ <App />
79
+ </HistoryRouterProvider>
80
+ ```
81
+
82
+ ### `useNavigation()`
83
+
84
+ Hook to access navigation functions and state.
85
+
86
+ ```tsx
87
+ const {
88
+ navigate, // Navigate to a path
89
+ goBack, // Go back in history
90
+ goForward, // Go forward in history
91
+ pathname, // Current pathname
92
+ searchParams, // URL search params
93
+ hash, // URL hash
94
+ } = useNavigation()
95
+ ```
96
+
97
+ #### Methods
98
+
99
+ - **`navigate(path: string, options?)`** - Navigate to a new path
100
+ - `options.replace?: boolean` - Replace current history entry
101
+ - `options.state?: any` - Pass state to the new route
102
+
103
+ - **`goBack()`** - Navigate back in history
104
+ - **`goForward()`** - Navigate forward in history
105
+
106
+ ### View Transitions (Web only)
107
+
108
+ Pepper automatically uses the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) when available for smooth page transitions.
109
+
110
+ ```tsx
111
+ // Transitions happen automatically!
112
+ navigate("/about") // ✨ Smooth transition
113
+ ```
114
+
115
+ ## 🎯 Why Pepper?
116
+
117
+ Most routers are built for one platform. Pepper works everywhere:
118
+
119
+ | Feature | Pepper | React Router | Expo Router |
120
+ | ------------------ | ------ | ------------ | ----------- |
121
+ | Web | ✅ | ✅ | ❌ |
122
+ | React Native | ✅ | ❌ | ✅ |
123
+ | Browser Extensions | ✅ | ❌ | ❌ |
124
+ | View Transitions | ✅ | ❌ | ❌ |
125
+ | Zero Config | ✅ | ❌ | ❌ |
126
+
127
+ ## 🏗️ Platform Detection
128
+
129
+ Pepper automatically detects your platform and uses the appropriate navigation method:
130
+
131
+ - **Web**: Uses `window.history` API with view transitions
132
+ - **React Native**: Uses in-memory history stack
133
+ - **Extensions**: Uses extension-compatible history management
134
+
135
+ ## 📱 Examples
136
+
137
+ Check out the [examples](./examples) directory for complete working examples:
138
+
139
+ - [Web App](./examples/web)
140
+ - [React Native App](./examples/native)
141
+ - [Browser Extension](./examples/extension)
142
+
143
+ ## 🤝 Contributing
144
+
145
+ Contributions are welcome! Please read our [Contributing Guide](./CONTRIBUTING.md) for details.
146
+
147
+ ## 📄 License
148
+
149
+ MIT © [Iliyan Velinov](https://github.com/askvex)
150
+
151
+ ## 🔗 Links
152
+
153
+ - [GitHub](https://github.com/askvex/pepper)
154
+ - [Issues](https://github.com/askvex/pepper/issues)
155
+ - [Vex - Powered by Pepper](https://askvex.com)
156
+
157
+ ---
158
+
159
+ Made with 🌶️ by the Vex team
@@ -0,0 +1,3 @@
1
+ export { ClientRouter, HistoryRouterProvider, NavigateOptions, RouterState, clientRouter, useHash, useHistoryRouter, useNavigation, usePathname, useRouter, useSearchParams } from './index.mjs';
2
+ import 'react/jsx-runtime';
3
+ import 'react';
@@ -0,0 +1,3 @@
1
+ export { ClientRouter, HistoryRouterProvider, NavigateOptions, RouterState, clientRouter, useHash, useHistoryRouter, useNavigation, usePathname, useRouter, useSearchParams } from './index.js';
2
+ import 'react/jsx-runtime';
3
+ import 'react';
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/extension.ts
21
+ var extension_exports = {};
22
+ __export(extension_exports, {
23
+ ClientRouter: () => ClientRouter,
24
+ HistoryRouterProvider: () => HistoryRouterProvider,
25
+ clientRouter: () => clientRouter,
26
+ useHash: () => useHash,
27
+ useHistoryRouter: () => useHistoryRouter,
28
+ useNavigation: () => useNavigation,
29
+ usePathname: () => usePathname,
30
+ useRouter: () => useRouter,
31
+ useSearchParams: () => useSearchParams
32
+ });
33
+ module.exports = __toCommonJS(extension_exports);
34
+
35
+ // src/core/ClientRouter.ts
36
+ var ClientRouter = class {
37
+ // Track mobile swipe gestures
38
+ constructor() {
39
+ this.listeners = /* @__PURE__ */ new Set();
40
+ this.isProgrammaticNavigation = false;
41
+ this.lastNavigationTime = 0;
42
+ this.navigationDebounceMs = 10;
43
+ // Prevent double-tap navigation
44
+ this.isSwipeNavigation = false;
45
+ this.handlePopState = () => {
46
+ if (this.isProgrammaticNavigation) {
47
+ return;
48
+ }
49
+ console.log("\u{1F336}\uFE0F Native back/forward navigation (no view transition)");
50
+ this.state = this.getCurrentState();
51
+ this.notifyListeners();
52
+ };
53
+ this.handleHashChange = () => {
54
+ this.state = this.getCurrentState();
55
+ this.notifyListeners();
56
+ };
57
+ this.state = typeof window !== "undefined" && window.__ROUTER_STATE__ ? window.__ROUTER_STATE__ : this.getCurrentState();
58
+ this.supportsViewTransitions = typeof document !== "undefined" && "startViewTransition" in document;
59
+ if (typeof window !== "undefined") {
60
+ console.log(
61
+ "\u{1F336}\uFE0F Pepper Router: View Transitions supported:",
62
+ this.supportsViewTransitions
63
+ );
64
+ }
65
+ if (typeof window === "undefined") return;
66
+ window.addEventListener("popstate", this.handlePopState, { passive: true });
67
+ window.addEventListener("hashchange", this.handleHashChange, {
68
+ passive: true
69
+ });
70
+ this.setupSwipeDetection();
71
+ }
72
+ getCurrentState() {
73
+ if (typeof window === "undefined") {
74
+ return {
75
+ pathname: "",
76
+ searchParams: new URLSearchParams(),
77
+ hash: ""
78
+ };
79
+ }
80
+ const url = new URL(window.location.href);
81
+ return {
82
+ pathname: url.pathname,
83
+ searchParams: url.searchParams,
84
+ hash: url.hash
85
+ };
86
+ }
87
+ notifyListeners() {
88
+ this.listeners.forEach((listener) => listener());
89
+ }
90
+ setupSwipeDetection() {
91
+ if (typeof window === "undefined") return;
92
+ let touchStartX = 0;
93
+ let touchStartY = 0;
94
+ window.addEventListener(
95
+ "touchstart",
96
+ (e) => {
97
+ if (e.touches[0]) {
98
+ touchStartX = e.touches[0].clientX;
99
+ touchStartY = e.touches[0].clientY;
100
+ }
101
+ },
102
+ { passive: true }
103
+ );
104
+ window.addEventListener(
105
+ "touchmove",
106
+ (e) => {
107
+ if (!e.touches[0]) return;
108
+ const touchEndX = e.touches[0].clientX;
109
+ const touchEndY = e.touches[0].clientY;
110
+ const deltaX = touchEndX - touchStartX;
111
+ const deltaY = touchEndY - touchStartY;
112
+ const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY);
113
+ const isFromEdge = touchStartX < 50 || touchStartX > window.innerWidth - 50;
114
+ if (isHorizontalSwipe && isFromEdge && Math.abs(deltaX) > 10) {
115
+ this.isSwipeNavigation = true;
116
+ console.log("\u{1F336}\uFE0F Swipe gesture detected - disabling view transitions");
117
+ }
118
+ },
119
+ { passive: true }
120
+ );
121
+ window.addEventListener(
122
+ "touchend",
123
+ () => {
124
+ setTimeout(() => {
125
+ this.isSwipeNavigation = false;
126
+ }, 500);
127
+ },
128
+ { passive: true }
129
+ );
130
+ }
131
+ subscribe(listener) {
132
+ this.listeners.add(listener);
133
+ return () => {
134
+ this.listeners.delete(listener);
135
+ };
136
+ }
137
+ push(href, options = {}) {
138
+ if (typeof window === "undefined") return;
139
+ const now = Date.now();
140
+ if (now - this.lastNavigationTime < this.navigationDebounceMs) {
141
+ console.log("\u{1F336}\uFE0F Navigation debounced (double-tap prevented)");
142
+ return;
143
+ }
144
+ this.lastNavigationTime = now;
145
+ this.isProgrammaticNavigation = true;
146
+ const url = new URL(href, window.location.origin);
147
+ const performNavigation = () => {
148
+ if (options.replace) {
149
+ window.history.replaceState({}, "", url.toString());
150
+ } else {
151
+ window.history.pushState({}, "", url.toString());
152
+ }
153
+ this.state = this.getCurrentState();
154
+ if (options.scroll !== false) {
155
+ window.scrollTo(0, 0);
156
+ }
157
+ this.notifyListeners();
158
+ queueMicrotask(() => {
159
+ this.isProgrammaticNavigation = false;
160
+ });
161
+ };
162
+ if (this.supportsViewTransitions && !options.shallow && !this.isSwipeNavigation) {
163
+ console.log("\u{1F336}\uFE0F Using View Transition for navigation to:", href);
164
+ document.startViewTransition(performNavigation);
165
+ } else {
166
+ if (this.isSwipeNavigation) {
167
+ console.log("\u{1F336}\uFE0F Swipe navigation - skipping view transition");
168
+ }
169
+ performNavigation();
170
+ }
171
+ }
172
+ replace(href, options = {}) {
173
+ this.push(href, { ...options, replace: true });
174
+ }
175
+ /**
176
+ * Prefetch a route to warm up the cache
177
+ * Useful for hover/focus prefetching
178
+ */
179
+ prefetch(url) {
180
+ if (typeof window === "undefined") return;
181
+ try {
182
+ fetch(url, { method: "HEAD", mode: "no-cors" }).catch(() => {
183
+ });
184
+ } catch {
185
+ }
186
+ }
187
+ /**
188
+ * Get current router state (useful for SSR hydration)
189
+ */
190
+ getState() {
191
+ return this.state;
192
+ }
193
+ refresh() {
194
+ if (typeof window === "undefined") return;
195
+ this.state = this.getCurrentState();
196
+ this.notifyListeners();
197
+ window.scrollTo(0, 0);
198
+ }
199
+ back() {
200
+ if (typeof window === "undefined") return;
201
+ window.history.back();
202
+ }
203
+ forward() {
204
+ if (typeof window === "undefined") return;
205
+ window.history.forward();
206
+ }
207
+ destroy() {
208
+ if (typeof window === "undefined") return;
209
+ window.removeEventListener("popstate", this.handlePopState);
210
+ window.removeEventListener("hashchange", this.handleHashChange);
211
+ this.listeners.clear();
212
+ }
213
+ // Get cached View Transitions support status
214
+ hasViewTransitions() {
215
+ return this.supportsViewTransitions;
216
+ }
217
+ };
218
+ var clientRouter = new ClientRouter();
219
+
220
+ // src/providers/HistoryRouterProvider.tsx
221
+ var import_react = require("react");
222
+ var import_jsx_runtime = require("react/jsx-runtime");
223
+ var HistoryRouterContext = (0, import_react.createContext)(
224
+ null
225
+ );
226
+ function useHistoryRouter() {
227
+ const context = (0, import_react.useContext)(HistoryRouterContext);
228
+ if (!context) {
229
+ throw new Error(
230
+ "useHistoryRouter must be used within HistoryRouterProvider"
231
+ );
232
+ }
233
+ return context;
234
+ }
235
+ function HistoryRouterProvider({
236
+ children
237
+ }) {
238
+ const [state, setState] = (0, import_react.useState)(() => clientRouter.getState());
239
+ const [updateTrigger, setUpdateTrigger] = (0, import_react.useState)(0);
240
+ (0, import_react.useEffect)(() => {
241
+ const unsubscribe = clientRouter.subscribe(() => {
242
+ const newState = clientRouter.getState();
243
+ setState(newState);
244
+ setUpdateTrigger((prev) => prev + 1);
245
+ });
246
+ return () => {
247
+ unsubscribe();
248
+ };
249
+ }, []);
250
+ const forceUpdate = () => {
251
+ setState(clientRouter.getState());
252
+ setUpdateTrigger((prev) => prev + 1);
253
+ };
254
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
255
+ HistoryRouterContext.Provider,
256
+ {
257
+ value: {
258
+ pathname: state.pathname,
259
+ searchParams: state.searchParams,
260
+ hash: state.hash,
261
+ forceUpdate
262
+ },
263
+ children
264
+ }
265
+ );
266
+ }
267
+
268
+ // src/hooks/useNavigation.ts
269
+ var import_react2 = require("react");
270
+ function useRouter() {
271
+ return {
272
+ push: clientRouter.push.bind(clientRouter),
273
+ replace: clientRouter.replace.bind(clientRouter),
274
+ back: clientRouter.back.bind(clientRouter),
275
+ forward: clientRouter.forward.bind(clientRouter),
276
+ refresh: clientRouter.refresh.bind(clientRouter),
277
+ prefetch: clientRouter.prefetch.bind(clientRouter)
278
+ };
279
+ }
280
+ function usePathname() {
281
+ const [pathname, setPathname] = (0, import_react2.useState)(clientRouter.getState().pathname);
282
+ (0, import_react2.useEffect)(() => {
283
+ const unsubscribe = clientRouter.subscribe(() => {
284
+ setPathname(clientRouter.getState().pathname);
285
+ });
286
+ return () => {
287
+ unsubscribe();
288
+ };
289
+ }, []);
290
+ return pathname;
291
+ }
292
+ function useSearchParams() {
293
+ const [searchParams, setSearchParams] = (0, import_react2.useState)(
294
+ clientRouter.getState().searchParams
295
+ );
296
+ (0, import_react2.useEffect)(() => {
297
+ const unsubscribe = clientRouter.subscribe(() => {
298
+ setSearchParams(clientRouter.getState().searchParams);
299
+ });
300
+ return () => {
301
+ unsubscribe();
302
+ };
303
+ }, []);
304
+ return searchParams;
305
+ }
306
+ function useHash() {
307
+ const [hash, setHash] = (0, import_react2.useState)(clientRouter.getState().hash);
308
+ (0, import_react2.useEffect)(() => {
309
+ const unsubscribe = clientRouter.subscribe(() => {
310
+ setHash(clientRouter.getState().hash);
311
+ });
312
+ return () => {
313
+ unsubscribe();
314
+ };
315
+ }, []);
316
+ return hash;
317
+ }
318
+ function useNavigation() {
319
+ const router = useRouter();
320
+ const pathname = usePathname();
321
+ const searchParams = useSearchParams();
322
+ const hash = useHash();
323
+ return {
324
+ // Navigation methods
325
+ navigate: router.push,
326
+ replace: router.replace,
327
+ goBack: router.back,
328
+ goForward: router.forward,
329
+ refresh: router.refresh,
330
+ prefetch: router.prefetch,
331
+ // Current state
332
+ pathname,
333
+ searchParams,
334
+ hash
335
+ };
336
+ }
337
+ // Annotate the CommonJS export names for ESM import in node:
338
+ 0 && (module.exports = {
339
+ ClientRouter,
340
+ HistoryRouterProvider,
341
+ clientRouter,
342
+ useHash,
343
+ useHistoryRouter,
344
+ useNavigation,
345
+ usePathname,
346
+ useRouter,
347
+ useSearchParams
348
+ });
349
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/extension.ts","../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["/**\n * 🌶️ Pepper Router - Browser Extension\n * Optimized for browser extensions (Chrome, Firefox, etc.)\n */\n\nexport * from \"./index\"\n","// Types\nexport interface NavigateOptions {\n scroll?: boolean\n shallow?: boolean\n replace?: boolean // Use replaceState instead of pushState\n}\n\nexport interface RouterState {\n pathname: string\n searchParams: URLSearchParams\n hash: string\n}\n\n// SSR hydration state\ndeclare global {\n interface Window {\n __ROUTER_STATE__?: RouterState\n }\n}\n\nexport class ClientRouter {\n private listeners: Set<() => void> = new Set()\n private state: RouterState\n private isProgrammaticNavigation = false\n private supportsViewTransitions: boolean\n private lastNavigationTime = 0\n private navigationDebounceMs = 10 // Prevent double-tap navigation\n private isSwipeNavigation = false // Track mobile swipe gestures\n\n constructor() {\n // SSR hydration: Use server state if available\n this.state =\n typeof window !== \"undefined\" && window.__ROUTER_STATE__\n ? window.__ROUTER_STATE__\n : this.getCurrentState()\n\n // Cache View Transitions API support check\n this.supportsViewTransitions =\n typeof document !== \"undefined\" && \"startViewTransition\" in document\n\n // Log support status for debugging\n if (typeof window !== \"undefined\") {\n console.log(\n \"🌶️ Pepper Router: View Transitions supported:\",\n this.supportsViewTransitions,\n )\n }\n\n if (typeof window === \"undefined\") return\n\n // Listen to browser navigation events (passive for better performance)\n window.addEventListener(\"popstate\", this.handlePopState, { passive: true })\n window.addEventListener(\"hashchange\", this.handleHashChange, {\n passive: true,\n })\n\n // Detect mobile swipe gestures for back/forward navigation\n this.setupSwipeDetection()\n }\n\n private getCurrentState(): RouterState {\n if (typeof window === \"undefined\") {\n return {\n pathname: \"\",\n searchParams: new URLSearchParams(),\n hash: \"\",\n }\n }\n const url = new URL(window.location.href)\n return {\n pathname: url.pathname,\n searchParams: url.searchParams,\n hash: url.hash,\n }\n }\n\n private handlePopState = () => {\n // Ignore popstate during programmatic navigation\n if (this.isProgrammaticNavigation) {\n return\n }\n\n // Native browser back/forward (mobile swipe gestures, browser buttons)\n // Don't use view transitions - let browser handle it natively for smooth UX\n console.log(\"🌶️ Native back/forward navigation (no view transition)\")\n this.state = this.getCurrentState()\n this.notifyListeners()\n }\n\n private handleHashChange = () => {\n this.state = this.getCurrentState()\n this.notifyListeners()\n }\n\n private notifyListeners() {\n this.listeners.forEach((listener) => listener())\n }\n\n private setupSwipeDetection() {\n if (typeof window === \"undefined\") return\n\n let touchStartX = 0\n let touchStartY = 0\n\n window.addEventListener(\n \"touchstart\",\n (e) => {\n if (e.touches[0]) {\n touchStartX = e.touches[0].clientX\n touchStartY = e.touches[0].clientY\n }\n },\n { passive: true },\n )\n\n window.addEventListener(\n \"touchmove\",\n (e) => {\n if (!e.touches[0]) return\n\n const touchEndX = e.touches[0].clientX\n const touchEndY = e.touches[0].clientY\n\n const deltaX = touchEndX - touchStartX\n const deltaY = touchEndY - touchStartY\n\n // Detect horizontal swipe from screen edge (back/forward gesture)\n // iOS Safari: swipe from left edge = back, right edge = forward\n const isHorizontalSwipe = Math.abs(deltaX) > Math.abs(deltaY)\n const isFromEdge =\n touchStartX < 50 || touchStartX > window.innerWidth - 50\n\n if (isHorizontalSwipe && isFromEdge && Math.abs(deltaX) > 10) {\n this.isSwipeNavigation = true\n console.log(\"🌶️ Swipe gesture detected - disabling view transitions\")\n }\n },\n { passive: true },\n )\n\n window.addEventListener(\n \"touchend\",\n () => {\n // Reset swipe flag after a delay\n setTimeout(() => {\n this.isSwipeNavigation = false\n }, 500)\n },\n { passive: true },\n )\n }\n\n subscribe(listener: () => void) {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n push(href: string, options: NavigateOptions = {}) {\n if (typeof window === \"undefined\") return\n\n // Debounce: Prevent double navigation (mobile double-tap)\n const now = Date.now()\n if (now - this.lastNavigationTime < this.navigationDebounceMs) {\n console.log(\"🌶️ Navigation debounced (double-tap prevented)\")\n return\n }\n this.lastNavigationTime = now\n\n this.isProgrammaticNavigation = true\n const url = new URL(href, window.location.origin)\n\n const performNavigation = () => {\n // Support both push and replace\n if (options.replace) {\n window.history.replaceState({}, \"\", url.toString())\n } else {\n window.history.pushState({}, \"\", url.toString())\n }\n\n this.state = this.getCurrentState()\n\n if (options.scroll !== false) {\n window.scrollTo(0, 0)\n }\n\n this.notifyListeners()\n\n // Use queueMicrotask for better performance than setTimeout\n queueMicrotask(() => {\n this.isProgrammaticNavigation = false\n })\n }\n\n // Use cached View Transitions API check\n // Skip transitions during swipe gestures to avoid conflicts with native animations\n if (\n this.supportsViewTransitions &&\n !options.shallow &&\n !this.isSwipeNavigation\n ) {\n console.log(\"🌶️ Using View Transition for navigation to:\", href)\n document.startViewTransition(performNavigation)\n } else {\n if (this.isSwipeNavigation) {\n console.log(\"🌶️ Swipe navigation - skipping view transition\")\n }\n performNavigation()\n }\n }\n\n replace(href: string, options: NavigateOptions = {}) {\n // Delegate to push with replace option\n this.push(href, { ...options, replace: true })\n }\n\n /**\n * Prefetch a route to warm up the cache\n * Useful for hover/focus prefetching\n */\n prefetch(url: string) {\n if (typeof window === \"undefined\") return\n\n try {\n // HEAD request to warm up cache without downloading full content\n fetch(url, { method: \"HEAD\", mode: \"no-cors\" }).catch(() => {\n // Silently fail - prefetch is a hint, not critical\n })\n } catch {\n // Ignore prefetch errors\n }\n }\n\n /**\n * Get current router state (useful for SSR hydration)\n */\n getState(): RouterState {\n return this.state\n }\n\n refresh() {\n if (typeof window === \"undefined\") return\n // Force refresh by updating state and notifying listeners\n this.state = this.getCurrentState()\n this.notifyListeners()\n // Scroll to top on refresh\n window.scrollTo(0, 0)\n }\n\n back() {\n if (typeof window === \"undefined\") return\n window.history.back()\n }\n\n forward() {\n if (typeof window === \"undefined\") return\n window.history.forward()\n }\n\n destroy() {\n if (typeof window === \"undefined\") return\n window.removeEventListener(\"popstate\", this.handlePopState)\n window.removeEventListener(\"hashchange\", this.handleHashChange)\n this.listeners.clear()\n }\n\n // Get cached View Transitions support status\n hasViewTransitions() {\n return this.supportsViewTransitions\n }\n}\n\n// Create singleton instance\nexport const clientRouter = new ClientRouter()\n","/**\n * HistoryRouterProvider - Makes window.history changes trigger React re-renders\n *\n * This provider ensures that when clientRouter.push() is called,\n * all components using pathname/searchParams will re-render properly\n */\n\nimport {\n createContext,\n useContext,\n useEffect,\n useState,\n ReactNode,\n} from \"react\"\nimport { clientRouter } from \"../core/ClientRouter\"\n\ninterface HistoryRouterContextValue {\n pathname: string\n searchParams: URLSearchParams\n hash: string\n forceUpdate: () => void\n}\n\nconst HistoryRouterContext = createContext<HistoryRouterContextValue | null>(\n null,\n)\n\nexport function useHistoryRouter() {\n const context = useContext(HistoryRouterContext)\n if (!context) {\n throw new Error(\n \"useHistoryRouter must be used within HistoryRouterProvider\",\n )\n }\n return context\n}\n\ninterface HistoryRouterProviderProps {\n children: ReactNode\n}\n\n/**\n * HistoryRouterProvider\n *\n * Wraps the app and listens to window.history changes,\n * triggering React re-renders when navigation occurs\n *\n * @example\n * ```tsx\n * <HistoryRouterProvider>\n * <App />\n * </HistoryRouterProvider>\n * ```\n */\nexport function HistoryRouterProvider({\n children,\n}: HistoryRouterProviderProps) {\n const [state, setState] = useState(() => clientRouter.getState())\n const [updateTrigger, setUpdateTrigger] = useState(0)\n\n useEffect(() => {\n // Subscribe to router changes\n const unsubscribe = clientRouter.subscribe(() => {\n const newState = clientRouter.getState()\n setState(newState)\n setUpdateTrigger((prev) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev) => prev + 1)\n }\n\n return (\n <HistoryRouterContext.Provider\n value={{\n pathname: state.pathname,\n searchParams: state.searchParams,\n hash: state.hash,\n forceUpdate,\n }}\n >\n {children}\n </HistoryRouterContext.Provider>\n )\n}\n","import { useEffect, useState } from \"react\"\nimport { clientRouter, NavigateOptions } from \"../core/ClientRouter\"\n\nexport function useRouter() {\n return {\n push: clientRouter.push.bind(clientRouter),\n replace: clientRouter.replace.bind(clientRouter),\n back: clientRouter.back.bind(clientRouter),\n forward: clientRouter.forward.bind(clientRouter),\n refresh: clientRouter.refresh.bind(clientRouter),\n prefetch: clientRouter.prefetch.bind(clientRouter),\n }\n}\n\nexport function usePathname() {\n const [pathname, setPathname] = useState(clientRouter.getState().pathname)\n\n useEffect(() => {\n const unsubscribe = clientRouter.subscribe(() => {\n setPathname(clientRouter.getState().pathname)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n return pathname\n}\n\nexport function useSearchParams() {\n const [searchParams, setSearchParams] = useState(\n clientRouter.getState().searchParams,\n )\n\n useEffect(() => {\n const unsubscribe = clientRouter.subscribe(() => {\n setSearchParams(clientRouter.getState().searchParams)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n return searchParams\n}\n\nexport function useHash() {\n const [hash, setHash] = useState(clientRouter.getState().hash)\n\n useEffect(() => {\n const unsubscribe = clientRouter.subscribe(() => {\n setHash(clientRouter.getState().hash)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n return hash\n}\n\n/**\n * Main navigation hook - provides all navigation methods and state\n *\n * @example\n * ```tsx\n * const { navigate, pathname, searchParams } = useNavigation()\n *\n * navigate('/about')\n * navigate('/search?q=pepper', { replace: true })\n * ```\n */\nexport function useNavigation() {\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const hash = useHash()\n\n return {\n // Navigation methods\n navigate: router.push,\n replace: router.replace,\n goBack: router.back,\n goForward: router.forward,\n refresh: router.refresh,\n prefetch: router.prefetch,\n\n // Current state\n pathname,\n searchParams,\n hash,\n }\n}\n"],"mappings":"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","names":["import_react"]}