@chrryai/pepper 1.2.69 → 1.2.70
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/dist/extension.js.map +1 -1
- package/dist/extension.mjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/native.js.map +1 -1
- package/dist/native.mjs.map +1 -1
- package/dist/web.js.map +1 -1
- package/dist/web.mjs.map +1 -1
- package/package.json +6 -4
package/dist/extension.js.map
CHANGED
|
@@ -1 +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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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"]}
|
|
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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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"]}
|
package/dist/extension.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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":["useEffect","useState","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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":["useEffect","useState","useState","useEffect"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["/**\n * 🌶️ Pepper Router\n * Universal router for React - works in web, React Native, and browser extensions\n */\n\n// Core\nexport { ClientRouter, clientRouter } from \"./core/ClientRouter\"\nexport type { NavigateOptions, RouterState } from \"./core/ClientRouter\"\n\n// Provider\nexport {\n HistoryRouterProvider,\n useHistoryRouter,\n} from \"./providers/HistoryRouterProvider\"\n\n// Hooks\nexport {\n useNavigation,\n useRouter,\n usePathname,\n useSearchParams,\n useHash,\n} from \"./hooks/useNavigation\"\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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["/**\n * 🌶️ Pepper Router\n * Universal router for React - works in web, React Native, and browser extensions\n */\n\n// Core\nexport { ClientRouter, clientRouter } from \"./core/ClientRouter\"\nexport type { NavigateOptions, RouterState } from \"./core/ClientRouter\"\n\n// Provider\nexport {\n HistoryRouterProvider,\n useHistoryRouter,\n} from \"./providers/HistoryRouterProvider\"\n\n// Hooks\nexport {\n useNavigation,\n useRouter,\n usePathname,\n useSearchParams,\n useHash,\n} from \"./hooks/useNavigation\"\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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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"]}
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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":["useEffect","useState","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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":["useEffect","useState","useState","useEffect"]}
|
package/dist/native.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/native.ts","../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["/**\n * 🌶️ Pepper Router - React Native\n * Optimized for React Native applications\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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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"]}
|
|
1
|
+
{"version":3,"sources":["../src/native.ts","../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["/**\n * 🌶️ Pepper Router - React Native\n * Optimized for React Native applications\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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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"]}
|
package/dist/native.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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":["useEffect","useState","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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":["useEffect","useState","useState","useEffect"]}
|
package/dist/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web.ts","../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["/**\n * 🌶️ Pepper Router - Web\n * Optimized for web applications with View Transitions API support\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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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"]}
|
|
1
|
+
{"version":3,"sources":["../src/web.ts","../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["/**\n * 🌶️ Pepper Router - Web\n * Optimized for web applications with View Transitions API support\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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACoBO,IAAM,eAAN,MAAmB;AAAA;AAAA,EASxB,cAAc;AARd,SAAQ,YAA6B,oBAAI,IAAI;AAE7C,SAAQ,2BAA2B;AAEnC,SAAQ,qBAAqB;AAC7B,SAAQ,uBAAuB;AAC/B;AAAA,SAAQ,oBAAoB;AAmD5B,SAAQ,iBAAiB,MAAM;AAE7B,UAAI,KAAK,0BAA0B;AACjC;AAAA,MACF;AAIA,cAAQ,IAAI,qEAAyD;AACrE,WAAK,QAAQ,KAAK,gBAAgB;AAClC,WAAK,gBAAgB;AAAA,IACvB;AAEA,SAAQ,mBAAmB,MAAM;AAC/B,WAAK,QAAQ,KAAK,gBAAgB;AAClC,WAAK,gBAAgB;AAAA,IACvB;AA/DE,SAAK,QACH,OAAO,WAAW,eAAe,OAAO,mBACpC,OAAO,mBACP,KAAK,gBAAgB;AAG3B,SAAK,0BACH,OAAO,aAAa,eAAe,yBAAyB;AAG9D,QAAI,OAAO,WAAW,aAAa;AACjC,cAAQ;AAAA,QACN;AAAA,QACA,KAAK;AAAA,MACP;AAAA,IACF;AAEA,QAAI,OAAO,WAAW,YAAa;AAGnC,WAAO,iBAAiB,YAAY,KAAK,gBAAgB,EAAE,SAAS,KAAK,CAAC;AAC1E,WAAO,iBAAiB,cAAc,KAAK,kBAAkB;AAAA,MAC3D,SAAS;AAAA,IACX,CAAC;AAGD,SAAK,oBAAoB;AAAA,EAC3B;AAAA,EAEQ,kBAA+B;AACrC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO;AAAA,QACL,UAAU;AAAA,QACV,cAAc,IAAI,gBAAgB;AAAA,QAClC,MAAM;AAAA,MACR;AAAA,IACF;AACA,UAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AACxC,UAAM,WAAW,IAAI,aAAa,gBAAgB,MAAM,IAAI,YAAY;AAExE,WAAO;AAAA,MACL;AAAA,MACA,cAAc,IAAI;AAAA,MAClB,MAAM,IAAI;AAAA,IACZ;AAAA,EACF;AAAA,EAoBQ,kBAAkB;AACxB,SAAK,UAAU,QAAQ,CAAC,aAAa,SAAS,CAAC;AAAA,EACjD;AAAA,EAEQ,sBAAsB;AAC5B,QAAI,OAAO,WAAW,YAAa;AAEnC,QAAI,cAAc;AAClB,QAAI,cAAc;AAElB,WAAO;AAAA,MACL;AAAA,MACA,CAAC,MAAM;AACL,YAAI,EAAE,QAAQ,CAAC,GAAG;AAChB,wBAAc,EAAE,QAAQ,CAAC,EAAE;AAC3B,wBAAc,EAAE,QAAQ,CAAC,EAAE;AAAA,QAC7B;AAAA,MACF;AAAA,MACA,EAAE,SAAS,KAAK;AAAA,IAClB;AAEA,WAAO;AAAA,MACL;AAAA,MACA,CAAC,MAAM;AACL,YAAI,CAAC,EAAE,QAAQ,CAAC,EAAG;AAEnB,cAAM,YAAY,EAAE,QAAQ,CAAC,EAAE;AAC/B,cAAM,YAAY,EAAE,QAAQ,CAAC,EAAE;AAE/B,cAAM,SAAS,YAAY;AAC3B,cAAM,SAAS,YAAY;AAI3B,cAAM,oBAAoB,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,MAAM;AAC5D,cAAM,aACJ,cAAc,MAAM,cAAc,OAAO,aAAa;AAExD,YAAI,qBAAqB,cAAc,KAAK,IAAI,MAAM,IAAI,IAAI;AAC5D,eAAK,oBAAoB;AACzB,kBAAQ,IAAI,qEAAyD;AAAA,QACvE;AAAA,MACF;AAAA,MACA,EAAE,SAAS,KAAK;AAAA,IAClB;AAEA,WAAO;AAAA,MACL;AAAA,MACA,MAAM;AAEJ,mBAAW,MAAM;AACf,eAAK,oBAAoB;AAAA,QAC3B,GAAG,GAAG;AAAA,MACR;AAAA,MACA,EAAE,SAAS,KAAK;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,UAAU,UAAsB;AAC9B,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,KAAK,MAAc,UAA2B,CAAC,GAAG;AAChD,QAAI,OAAO,WAAW,YAAa;AAGnC,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,MAAM,KAAK,qBAAqB,KAAK,sBAAsB;AAC7D,cAAQ,IAAI,6DAAiD;AAC7D;AAAA,IACF;AACA,SAAK,qBAAqB;AAE1B,SAAK,2BAA2B;AAChC,UAAM,MAAM,IAAI,IAAI,MAAM,OAAO,SAAS,MAAM;AAEhD,UAAM,oBAAoB,MAAM;AAE9B,UAAI,QAAQ,SAAS;AACnB,eAAO,QAAQ,aAAa,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAAA,MACpD,OAAO;AACL,eAAO,QAAQ,UAAU,CAAC,GAAG,IAAI,IAAI,SAAS,CAAC;AAAA,MACjD;AAEA,WAAK,QAAQ,KAAK,gBAAgB;AAElC,UAAI,QAAQ,WAAW,OAAO;AAC5B,eAAO,SAAS,GAAG,CAAC;AAAA,MACtB;AAEA,WAAK,gBAAgB;AAGrB,qBAAe,MAAM;AACnB,aAAK,2BAA2B;AAAA,MAClC,CAAC;AAAA,IACH;AAIA,QACE,KAAK,2BACL,CAAC,QAAQ,WACT,CAAC,KAAK,mBACN;AACA,cAAQ,IAAI,4DAAgD,IAAI;AAChE,eAAS,oBAAoB,iBAAiB;AAAA,IAChD,OAAO;AACL,UAAI,KAAK,mBAAmB;AAC1B,gBAAQ,IAAI,6DAAiD;AAAA,MAC/D;AACA,wBAAkB;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,QAAQ,MAAc,UAA2B,CAAC,GAAG;AAEnD,SAAK,KAAK,MAAM,EAAE,GAAG,SAAS,SAAS,KAAK,CAAC;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,KAAa;AACpB,QAAI,OAAO,WAAW,YAAa;AAEnC,QAAI;AAEF,YAAM,KAAK,EAAE,QAAQ,QAAQ,MAAM,UAAU,CAAC,EAAE,MAAM,MAAM;AAAA,MAE5D,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,WAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU;AACR,QAAI,OAAO,WAAW,YAAa;AAEnC,SAAK,QAAQ,KAAK,gBAAgB;AAClC,SAAK,gBAAgB;AAErB,WAAO,SAAS,GAAG,CAAC;AAAA,EACtB;AAAA,EAEA,OAAO;AACL,QAAI,OAAO,WAAW,YAAa;AACnC,WAAO,QAAQ,KAAK;AAAA,EACtB;AAAA,EAEA,UAAU;AACR,QAAI,OAAO,WAAW,YAAa;AACnC,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,UAAU;AACR,QAAI,OAAO,WAAW,YAAa;AACnC,WAAO,oBAAoB,YAAY,KAAK,cAAc;AAC1D,WAAO,oBAAoB,cAAc,KAAK,gBAAgB;AAC9D,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA;AAAA,EAGA,qBAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AACF;AAGO,IAAM,eAAe,IAAI,aAAa;;;AC7Q7C,mBAMO;AAkEH;AAxDJ,IAAM,2BAAuB;AAAA,EAC3B;AACF;AAEO,SAAS,mBAAmB;AACjC,QAAM,cAAU,yBAAW,oBAAoB;AAC/C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAmBO,SAAS,sBAAsB;AAAA,EACpC;AACF,GAA+B;AAC7B,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,MAAM,aAAa,SAAS,CAAC;AAChE,QAAM,CAAC,eAAe,gBAAgB,QAAI,uBAAS,CAAC;AAEpD,8BAAU,MAAM;AAEd,UAAM,cAAc,aAAa,UAAU,MAAM;AAC/C,YAAM,WAAW,aAAa,SAAS;AACvC,eAAS,QAAQ;AACjB,uBAAiB,CAAC,SAAiB,OAAO,CAAC;AAAA,IAC7C,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,MAAM;AACxB,aAAS,aAAa,SAAS,CAAC;AAChC,qBAAiB,CAAC,SAAiB,OAAO,CAAC;AAAA,EAC7C;AAEA,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC,OAAO;AAAA,QACL,UAAU,MAAM;AAAA,QAChB,cAAc,MAAM;AAAA,QACpB,MAAM,MAAM;AAAA,QACZ;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;;;AC1FA,IAAAA,gBAAoC;AAG7B,SAAS,YAAY;AAC1B,SAAO;AAAA,IACL,MAAM,aAAa,KAAK,KAAK,YAAY;AAAA,IACzC,SAAS,aAAa,QAAQ,KAAK,YAAY;AAAA,IAC/C,MAAM,aAAa,KAAK,KAAK,YAAY;AAAA,IACzC,SAAS,aAAa,QAAQ,KAAK,YAAY;AAAA,IAC/C,SAAS,aAAa,QAAQ,KAAK,YAAY;AAAA,IAC/C,UAAU,aAAa,SAAS,KAAK,YAAY;AAAA,EACnD;AACF;AAEO,SAAS,cAAc;AAC5B,QAAM,CAAC,UAAU,WAAW,QAAI,wBAAS,aAAa,SAAS,EAAE,QAAQ;AAEzE,+BAAU,MAAM;AACd,UAAM,cAAc,aAAa,UAAU,MAAM;AAC/C,kBAAY,aAAa,SAAS,EAAE,QAAQ;AAAA,IAC9C,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;AAEO,SAAS,kBAAkB;AAChC,QAAM,CAAC,cAAc,eAAe,QAAI;AAAA,IACtC,aAAa,SAAS,EAAE;AAAA,EAC1B;AAEA,+BAAU,MAAM;AACd,UAAM,cAAc,aAAa,UAAU,MAAM;AAC/C,sBAAgB,aAAa,SAAS,EAAE,YAAY;AAAA,IACtD,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;AAEO,SAAS,UAAU;AACxB,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAS,aAAa,SAAS,EAAE,IAAI;AAE7D,+BAAU,MAAM;AACd,UAAM,cAAc,aAAa,UAAU,MAAM;AAC/C,cAAQ,aAAa,SAAS,EAAE,IAAI;AAAA,IACtC,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;AAaO,SAAS,gBAAgB;AAC9B,QAAM,SAAS,UAAU;AACzB,QAAM,WAAW,YAAY;AAC7B,QAAM,eAAe,gBAAgB;AACrC,QAAM,OAAO,QAAQ;AAErB,SAAO;AAAA;AAAA,IAEL,UAAU,OAAO;AAAA,IACjB,SAAS,OAAO;AAAA,IAChB,QAAQ,OAAO;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,SAAS,OAAO;AAAA,IAChB,UAAU,OAAO;AAAA;AAAA,IAGjB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["import_react"]}
|
package/dist/web.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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":["useEffect","useState","useState","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/ClientRouter.ts","../src/providers/HistoryRouterProvider.tsx","../src/hooks/useNavigation.ts"],"sourcesContent":["// 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 const pathname = url.pathname === \"/index.html\" ? \"/\" : url.pathname || \"/\"\n\n return {\n 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: number) => prev + 1)\n })\n\n return () => {\n unsubscribe()\n }\n }, [])\n\n const forceUpdate = () => {\n setState(clientRouter.getState())\n setUpdateTrigger((prev: number) => 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":["useEffect","useState","useState","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chrryai/pepper",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.70",
|
|
4
4
|
"description": "Universal router for React - works in web, React Native, and browser extensions with built-in view transitions",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"LICENSE"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "tsup",
|
|
37
|
-
"dev": "tsup --watch",
|
|
36
|
+
"build": "pnpm exec tsup",
|
|
37
|
+
"dev": "pnpm exec tsup --watch",
|
|
38
38
|
"lint": "eslint src",
|
|
39
39
|
"type-check": "tsc --noEmit"
|
|
40
40
|
},
|
|
@@ -66,8 +66,10 @@
|
|
|
66
66
|
"peerDependencies": {
|
|
67
67
|
"react": "^18.0.0 || ^19.0.0"
|
|
68
68
|
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"@types/react": "^19.1.12"
|
|
71
|
+
},
|
|
69
72
|
"devDependencies": {
|
|
70
|
-
"@types/react": "^18.3.18",
|
|
71
73
|
"@typescript-eslint/parser": "^8.0.0",
|
|
72
74
|
"eslint": "^9.0.0",
|
|
73
75
|
"tsup": "^8.0.0",
|