@flexireact/core 3.0.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/dist/cli/index.js +17 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/core/client/index.js +12 -15
- package/dist/core/client/index.js.map +1 -1
- package/dist/core/index.js +94 -51
- package/dist/core/index.js.map +1 -1
- package/dist/core/server/index.js +3 -0
- package/dist/core/server/index.js.map +1 -1
- package/dist/core/start-dev.js +3 -0
- package/dist/core/start-dev.js.map +1 -1
- package/dist/core/start-prod.js +3 -0
- package/dist/core/start-prod.js.map +1 -1
- package/package.json +9 -7
|
@@ -10,22 +10,19 @@ function hydrateIsland(islandId, Component, props) {
|
|
|
10
10
|
if (element.hasAttribute("data-hydrated")) {
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}));
|
|
20
|
-
} catch (error) {
|
|
21
|
-
console.error(`Failed to hydrate island ${islandId}:`, error);
|
|
22
|
-
try {
|
|
23
|
-
createRoot(element).render(React.createElement(Component, props));
|
|
24
|
-
element.setAttribute("data-hydrated", "true");
|
|
25
|
-
} catch (fallbackError) {
|
|
26
|
-
console.error(`Fallback render also failed for ${islandId}:`, fallbackError);
|
|
13
|
+
hydrateRoot(element, React.createElement(Component, props), {
|
|
14
|
+
onRecoverableError: (error, errorInfo) => {
|
|
15
|
+
console.warn(`[FlexiReact] Hydration mismatch in ${islandId}:`, error);
|
|
16
|
+
if (process.env.NODE_ENV === "development") {
|
|
17
|
+
console.debug("Component stack:", errorInfo.componentStack);
|
|
18
|
+
}
|
|
27
19
|
}
|
|
28
|
-
}
|
|
20
|
+
});
|
|
21
|
+
element.setAttribute("data-hydrated", "true");
|
|
22
|
+
element.dispatchEvent(new CustomEvent("flexi:hydrated", {
|
|
23
|
+
bubbles: true,
|
|
24
|
+
detail: { islandId, props }
|
|
25
|
+
}));
|
|
29
26
|
}
|
|
30
27
|
function hydrateApp(App, props = {}) {
|
|
31
28
|
const root = document.getElementById("root");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../core/client/hydration.ts","../../../core/client/navigation.ts","../../../core/client/islands.ts","../../../core/client/Link.tsx"],"sourcesContent":["/**\r\n * FlexiReact Client Hydration\r\n * Handles selective hydration of islands and full app hydration\r\n */\r\n\r\nimport React from 'react';\r\nimport { hydrateRoot, createRoot } from 'react-dom/client';\r\n\r\n// Extend Window interface for __FLEXI_DATA__\r\ndeclare global {\r\n interface Window {\r\n __FLEXI_DATA__?: {\r\n islands?: any[];\r\n props?: Record<string, any>;\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Hydrates a specific island component\r\n */\r\nexport function hydrateIsland(islandId, Component, props) {\r\n const element = document.querySelector(`[data-island=\"${islandId}\"]`);\r\n \r\n if (!element) {\r\n console.warn(`Island element not found: ${islandId}`);\r\n return;\r\n }\r\n\r\n if (element.hasAttribute('data-hydrated')) {\r\n return; // Already hydrated\r\n }\r\n\r\n try {\r\n hydrateRoot(element, React.createElement(Component, props));\r\n element.setAttribute('data-hydrated', 'true');\r\n \r\n // Dispatch custom event\r\n element.dispatchEvent(new CustomEvent('flexi:hydrated', {\r\n bubbles: true,\r\n detail: { islandId, props }\r\n }));\r\n } catch (error) {\r\n console.error(`Failed to hydrate island ${islandId}:`, error);\r\n \r\n // Fallback: try full render instead of hydration\r\n try {\r\n createRoot(element).render(React.createElement(Component, props));\r\n element.setAttribute('data-hydrated', 'true');\r\n } catch (fallbackError) {\r\n console.error(`Fallback render also failed for ${islandId}:`, fallbackError);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Hydrates the entire application\r\n */\r\nexport function hydrateApp(App, props = {}) {\r\n const root = document.getElementById('root');\r\n \r\n if (!root) {\r\n console.error('Root element not found');\r\n return;\r\n }\r\n\r\n // Get server-rendered props\r\n const serverProps = window.__FLEXI_DATA__?.props || {};\r\n const mergedProps = { ...serverProps, ...props };\r\n\r\n try {\r\n hydrateRoot(root, React.createElement(App, mergedProps));\r\n } catch (error) {\r\n console.error('Hydration failed, falling back to full render:', error);\r\n createRoot(root).render(React.createElement(App, mergedProps));\r\n }\r\n}\r\n\r\n/**\r\n * Hydrates all islands on the page\r\n */\r\nexport async function hydrateAllIslands(islandModules) {\r\n const islands = document.querySelectorAll('[data-island]');\r\n \r\n for (const element of islands) {\r\n if (element.hasAttribute('data-hydrated')) continue;\r\n \r\n const islandId = element.getAttribute('data-island');\r\n const islandName = element.getAttribute('data-island-name');\r\n const propsJson = element.getAttribute('data-island-props');\r\n \r\n try {\r\n const props = propsJson ? JSON.parse(propsJson) : {};\r\n const module = islandModules[islandName];\r\n \r\n if (module) {\r\n hydrateIsland(islandId, module.default || module, props);\r\n }\r\n } catch (error) {\r\n console.error(`Failed to hydrate island ${islandName}:`, error);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Progressive hydration based on visibility\r\n */\r\nexport function setupProgressiveHydration(islandModules) {\r\n const observer = new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach(async (entry) => {\r\n if (!entry.isIntersecting) return;\r\n \r\n const element = entry.target;\r\n if (element.hasAttribute('data-hydrated')) return;\r\n \r\n const islandName = element.getAttribute('data-island-name');\r\n const islandId = element.getAttribute('data-island');\r\n const propsJson = element.getAttribute('data-island-props');\r\n \r\n try {\r\n const props = propsJson ? JSON.parse(propsJson) : {};\r\n const module = await islandModules[islandName]();\r\n \r\n hydrateIsland(islandId, module.default || module, props);\r\n observer.unobserve(element);\r\n } catch (error) {\r\n console.error(`Failed to hydrate island ${islandName}:`, error);\r\n }\r\n });\r\n },\r\n { rootMargin: '50px' }\r\n );\r\n\r\n document.querySelectorAll('[data-island]:not([data-hydrated])').forEach(el => {\r\n observer.observe(el);\r\n });\r\n\r\n return observer;\r\n}\r\n\r\nexport default {\r\n hydrateIsland,\r\n hydrateApp,\r\n hydrateAllIslands,\r\n setupProgressiveHydration\r\n};\r\n","/**\r\n * FlexiReact Client Navigation\r\n * Client-side navigation with prefetching\r\n */\r\n\r\nimport React from 'react';\r\n\r\n// Navigation state\r\nconst navigationState: {\r\n listeners: Set<(url: string) => void>;\r\n prefetched: Set<string>;\r\n} = {\r\n listeners: new Set(),\r\n prefetched: new Set()\r\n};\r\n\r\n/**\r\n * Navigates to a new URL\r\n */\r\ninterface NavigateOptions {\r\n replace?: boolean;\r\n scroll?: boolean;\r\n}\r\n\r\nexport function navigate(url: string, options: NavigateOptions = {}) {\r\n const { replace = false, scroll = true } = options;\r\n\r\n if (replace) {\r\n window.history.replaceState({}, '', url);\r\n } else {\r\n window.history.pushState({}, '', url);\r\n }\r\n\r\n // Dispatch navigation event\r\n window.dispatchEvent(new CustomEvent('flexi:navigate', {\r\n detail: { url, replace, scroll }\r\n }));\r\n\r\n // Scroll to top if needed\r\n if (scroll) {\r\n window.scrollTo(0, 0);\r\n }\r\n\r\n // Notify listeners\r\n navigationState.listeners.forEach(listener => listener(url));\r\n\r\n // Fetch and render new page\r\n return fetchAndRender(url);\r\n}\r\n\r\n/**\r\n * Prefetches a URL for faster navigation\r\n */\r\nexport function prefetch(url) {\r\n if (navigationState.prefetched.has(url)) {\r\n return Promise.resolve();\r\n }\r\n\r\n navigationState.prefetched.add(url);\r\n\r\n // Create a link element for prefetching\r\n const link = document.createElement('link');\r\n link.rel = 'prefetch';\r\n link.href = url;\r\n document.head.appendChild(link);\r\n\r\n return Promise.resolve();\r\n}\r\n\r\n/**\r\n * Fetches and renders a new page\r\n */\r\nasync function fetchAndRender(url) {\r\n try {\r\n const response = await fetch(url, {\r\n headers: {\r\n 'X-Flexi-Navigation': 'true'\r\n }\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Navigation failed: ${response.status}`);\r\n }\r\n\r\n const html = await response.text();\r\n \r\n // Parse the HTML\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(html, 'text/html');\r\n\r\n // Update the page content\r\n const newRoot = doc.getElementById('root');\r\n const currentRoot = document.getElementById('root');\r\n\r\n if (newRoot && currentRoot) {\r\n currentRoot.innerHTML = newRoot.innerHTML;\r\n }\r\n\r\n // Update the title\r\n document.title = doc.title;\r\n\r\n // Update meta tags\r\n updateMetaTags(doc);\r\n\r\n // Re-hydrate islands\r\n window.dispatchEvent(new CustomEvent('flexi:pageload'));\r\n\r\n } catch (error) {\r\n console.error('Navigation error:', error);\r\n // Fallback to full page navigation\r\n window.location.href = url;\r\n }\r\n}\r\n\r\n/**\r\n * Updates meta tags from new document\r\n */\r\nfunction updateMetaTags(doc) {\r\n // Remove old meta tags\r\n document.querySelectorAll('meta[data-flexi]').forEach(el => el.remove());\r\n\r\n // Add new meta tags\r\n doc.querySelectorAll('meta').forEach(meta => {\r\n if (meta.name || meta.property) {\r\n const newMeta = meta.cloneNode(true);\r\n newMeta.setAttribute('data-flexi', 'true');\r\n document.head.appendChild(newMeta);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Link component for client-side navigation\r\n */\r\nexport function Link({ href, children, prefetch: shouldPrefetch = true, replace = false, className, ...props }) {\r\n const handleClick = (e) => {\r\n // Allow normal navigation for external links or modified clicks\r\n if (\r\n e.ctrlKey ||\r\n e.metaKey ||\r\n e.shiftKey ||\r\n e.button !== 0 ||\r\n href.startsWith('http') ||\r\n href.startsWith('//')\r\n ) {\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n navigate(href, { replace });\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (shouldPrefetch) {\r\n prefetch(href);\r\n }\r\n };\r\n\r\n return React.createElement('a', {\r\n href,\r\n onClick: handleClick,\r\n onMouseEnter: handleMouseEnter,\r\n className,\r\n ...props\r\n }, children);\r\n}\r\n\r\n/**\r\n * Hook to listen for navigation events\r\n */\r\nexport function useNavigation() {\r\n const [pathname, setPathname] = React.useState(\r\n typeof window !== 'undefined' ? window.location.pathname : '/'\r\n );\r\n\r\n React.useEffect(() => {\r\n const handleNavigation = (url) => {\r\n setPathname(new URL(url, window.location.origin).pathname);\r\n };\r\n\r\n navigationState.listeners.add(handleNavigation);\r\n\r\n const handlePopState = () => {\r\n setPathname(window.location.pathname);\r\n navigationState.listeners.forEach(listener => listener(window.location.pathname));\r\n };\r\n\r\n window.addEventListener('popstate', handlePopState);\r\n\r\n return () => {\r\n navigationState.listeners.delete(handleNavigation);\r\n window.removeEventListener('popstate', handlePopState);\r\n };\r\n }, []);\r\n\r\n return { pathname, navigate, prefetch };\r\n}\r\n\r\n// Setup popstate listener\r\nif (typeof window !== 'undefined') {\r\n window.addEventListener('popstate', () => {\r\n const url = window.location.pathname + window.location.search;\r\n navigationState.listeners.forEach(listener => listener(url));\r\n });\r\n}\r\n\r\nexport default {\r\n navigate,\r\n prefetch,\r\n Link,\r\n useNavigation\r\n};\r\n","/**\r\n * FlexiReact Client Islands\r\n * Client-side island utilities\r\n */\r\n\r\nimport React from 'react';\r\n\r\n/**\r\n * Hook to check if component is hydrated\r\n */\r\nexport function useIsland() {\r\n const [isHydrated, setIsHydrated] = React.useState(false);\r\n\r\n React.useEffect(() => {\r\n setIsHydrated(true);\r\n }, []);\r\n\r\n return { isHydrated };\r\n}\r\n\r\n/**\r\n * Island boundary component\r\n * Wraps interactive components for partial hydration\r\n */\r\nexport function IslandBoundary({ children, fallback = null, name = 'island' }) {\r\n const { isHydrated } = useIsland();\r\n\r\n // On server or before hydration, render children normally\r\n // The server will wrap this in the island marker\r\n if (!isHydrated && fallback) {\r\n return fallback;\r\n }\r\n\r\n return React.createElement('div', {\r\n 'data-island-boundary': name,\r\n children\r\n });\r\n}\r\n\r\n/**\r\n * Creates a lazy-loaded island\r\n */\r\ninterface LazyIslandOptions {\r\n fallback?: React.ReactNode;\r\n name?: string;\r\n}\r\n\r\nexport function createClientIsland(loader: () => Promise<any>, options: LazyIslandOptions = {}) {\r\n const { fallback = null, name = 'lazy-island' } = options;\r\n\r\n return function LazyIsland(props) {\r\n const [Component, setComponent] = React.useState(null);\r\n const [error, setError] = React.useState(null);\r\n\r\n React.useEffect(() => {\r\n loader()\r\n .then(mod => setComponent(() => mod.default || mod))\r\n .catch(err => setError(err));\r\n }, []);\r\n\r\n if (error) {\r\n return React.createElement('div', {\r\n className: 'island-error',\r\n children: `Failed to load ${name}`\r\n });\r\n }\r\n\r\n if (!Component) {\r\n return fallback || React.createElement('div', {\r\n className: 'island-loading',\r\n children: 'Loading...'\r\n });\r\n }\r\n\r\n return React.createElement(Component, props);\r\n };\r\n}\r\n\r\n/**\r\n * Island with interaction trigger\r\n * Only hydrates when user interacts\r\n */\r\nexport function InteractiveIsland({ children, trigger = 'click', fallback }) {\r\n const [shouldHydrate, setShouldHydrate] = React.useState(false);\r\n const ref = React.useRef(null);\r\n\r\n React.useEffect(() => {\r\n const element = ref.current;\r\n if (!element) return;\r\n\r\n const handleInteraction = () => {\r\n setShouldHydrate(true);\r\n };\r\n\r\n element.addEventListener(trigger, handleInteraction, { once: true });\r\n\r\n return () => {\r\n element.removeEventListener(trigger, handleInteraction);\r\n };\r\n }, [trigger]);\r\n\r\n if (!shouldHydrate) {\r\n return React.createElement('div', {\r\n ref,\r\n 'data-interactive-island': 'true',\r\n children: fallback || children\r\n });\r\n }\r\n\r\n return children;\r\n}\r\n\r\n/**\r\n * Media query island\r\n * Only hydrates when media query matches\r\n */\r\nexport function MediaIsland({ children, query, fallback }) {\r\n const [matches, setMatches] = React.useState(false);\r\n\r\n React.useEffect(() => {\r\n const mediaQuery = window.matchMedia(query);\r\n setMatches(mediaQuery.matches);\r\n\r\n const handler = (e) => setMatches(e.matches);\r\n mediaQuery.addEventListener('change', handler);\r\n\r\n return () => mediaQuery.removeEventListener('change', handler);\r\n }, [query]);\r\n\r\n if (!matches) {\r\n return fallback || null;\r\n }\r\n\r\n return children;\r\n}\r\n\r\nexport default {\r\n useIsland,\r\n IslandBoundary,\r\n createClientIsland,\r\n InteractiveIsland,\r\n MediaIsland\r\n};\r\n","'use client';\r\n\r\n/**\r\n * FlexiReact Link Component\r\n * Enhanced link with prefetching, client-side navigation, and loading states\r\n */\r\n\r\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\r\n\r\nexport interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\r\n /** The URL to navigate to */\r\n href: string;\r\n /** Prefetch the page on hover/visibility */\r\n prefetch?: boolean | 'hover' | 'viewport';\r\n /** Replace the current history entry instead of pushing */\r\n replace?: boolean;\r\n /** Scroll to top after navigation */\r\n scroll?: boolean;\r\n /** Show loading indicator while navigating */\r\n showLoading?: boolean;\r\n /** Custom loading component */\r\n loadingComponent?: React.ReactNode;\r\n /** Callback when navigation starts */\r\n onNavigationStart?: () => void;\r\n /** Callback when navigation ends */\r\n onNavigationEnd?: () => void;\r\n /** Children */\r\n children: React.ReactNode;\r\n}\r\n\r\n// Prefetch cache to avoid duplicate requests\r\nconst prefetchCache = new Set<string>();\r\n\r\n// Prefetch a URL\r\nasync function prefetchUrl(url: string): Promise<void> {\r\n if (prefetchCache.has(url)) return;\r\n \r\n try {\r\n // Mark as prefetched immediately to prevent duplicate requests\r\n prefetchCache.add(url);\r\n \r\n // Use link preload for better browser optimization\r\n const link = document.createElement('link');\r\n link.rel = 'prefetch';\r\n link.href = url;\r\n link.as = 'document';\r\n document.head.appendChild(link);\r\n \r\n // Also fetch the page to warm the cache\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), 5000);\r\n \r\n await fetch(url, {\r\n method: 'GET',\r\n credentials: 'same-origin',\r\n signal: controller.signal,\r\n headers: {\r\n 'X-Flexi-Prefetch': '1',\r\n 'Accept': 'text/html'\r\n }\r\n });\r\n \r\n clearTimeout(timeoutId);\r\n } catch (error) {\r\n // Remove from cache on error so it can be retried\r\n prefetchCache.delete(url);\r\n }\r\n}\r\n\r\n// Check if URL is internal\r\nfunction isInternalUrl(url: string): boolean {\r\n if (url.startsWith('/')) return true;\r\n if (url.startsWith('#')) return true;\r\n \r\n try {\r\n const parsed = new URL(url, window.location.origin);\r\n return parsed.origin === window.location.origin;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n// Navigate to a URL\r\nfunction navigate(url: string, options: { replace?: boolean; scroll?: boolean } = {}): void {\r\n const { replace = false, scroll = true } = options;\r\n \r\n if (replace) {\r\n window.history.replaceState({}, '', url);\r\n } else {\r\n window.history.pushState({}, '', url);\r\n }\r\n \r\n // Dispatch popstate event to trigger any listeners\r\n window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));\r\n \r\n // Scroll to top if requested\r\n if (scroll) {\r\n window.scrollTo({ top: 0, behavior: 'smooth' });\r\n }\r\n}\r\n\r\n/**\r\n * Link component with prefetching and client-side navigation\r\n * \r\n * @example\r\n * ```tsx\r\n * import { Link } from '@flexireact/core/client';\r\n * \r\n * // Basic usage\r\n * <Link href=\"/about\">About</Link>\r\n * \r\n * // With prefetch on hover\r\n * <Link href=\"/products\" prefetch=\"hover\">Products</Link>\r\n * \r\n * // With prefetch on viewport visibility\r\n * <Link href=\"/contact\" prefetch=\"viewport\">Contact</Link>\r\n * \r\n * // Replace history instead of push\r\n * <Link href=\"/login\" replace>Login</Link>\r\n * \r\n * // Disable scroll to top\r\n * <Link href=\"/section#anchor\" scroll={false}>Go to section</Link>\r\n * ```\r\n */\r\nexport function Link({\r\n href,\r\n prefetch = true,\r\n replace = false,\r\n scroll = true,\r\n showLoading = false,\r\n loadingComponent,\r\n onNavigationStart,\r\n onNavigationEnd,\r\n children,\r\n className,\r\n onClick,\r\n onMouseEnter,\r\n onFocus,\r\n ...props\r\n}: LinkProps) {\r\n const [isNavigating, setIsNavigating] = useState(false);\r\n const linkRef = useRef<HTMLAnchorElement>(null);\r\n const hasPrefetched = useRef(false);\r\n\r\n // Prefetch on viewport visibility\r\n useEffect(() => {\r\n if (prefetch !== 'viewport' && prefetch !== true) return;\r\n if (!isInternalUrl(href)) return;\r\n if (hasPrefetched.current) return;\r\n\r\n const observer = new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting) {\r\n prefetchUrl(href);\r\n hasPrefetched.current = true;\r\n observer.disconnect();\r\n }\r\n });\r\n },\r\n { rootMargin: '200px' }\r\n );\r\n\r\n if (linkRef.current) {\r\n observer.observe(linkRef.current);\r\n }\r\n\r\n return () => observer.disconnect();\r\n }, [href, prefetch]);\r\n\r\n // Handle hover prefetch\r\n const handleMouseEnter = useCallback(\r\n (e: React.MouseEvent<HTMLAnchorElement>) => {\r\n onMouseEnter?.(e);\r\n \r\n if ((prefetch === 'hover' || prefetch === true) && isInternalUrl(href)) {\r\n prefetchUrl(href);\r\n }\r\n },\r\n [href, prefetch, onMouseEnter]\r\n );\r\n\r\n // Handle focus prefetch (for keyboard navigation)\r\n const handleFocus = useCallback(\r\n (e: React.FocusEvent<HTMLAnchorElement>) => {\r\n onFocus?.(e);\r\n \r\n if ((prefetch === 'hover' || prefetch === true) && isInternalUrl(href)) {\r\n prefetchUrl(href);\r\n }\r\n },\r\n [href, prefetch, onFocus]\r\n );\r\n\r\n // Handle click for client-side navigation\r\n const handleClick = useCallback(\r\n async (e: React.MouseEvent<HTMLAnchorElement>) => {\r\n onClick?.(e);\r\n \r\n // Don't handle if default was prevented\r\n if (e.defaultPrevented) return;\r\n \r\n // Don't handle if modifier keys are pressed (open in new tab, etc.)\r\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\r\n \r\n // Don't handle external URLs\r\n if (!isInternalUrl(href)) return;\r\n \r\n // Don't handle if target is set\r\n if (props.target && props.target !== '_self') return;\r\n \r\n // Prevent default navigation\r\n e.preventDefault();\r\n \r\n // Start navigation\r\n setIsNavigating(true);\r\n onNavigationStart?.();\r\n \r\n try {\r\n // Fetch the new page\r\n const response = await fetch(href, {\r\n method: 'GET',\r\n credentials: 'same-origin',\r\n headers: {\r\n 'X-Flexi-Navigation': '1',\r\n 'Accept': 'text/html'\r\n }\r\n });\r\n \r\n if (response.ok) {\r\n const html = await response.text();\r\n \r\n // Parse and update the page\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(html, 'text/html');\r\n \r\n // Update title\r\n const newTitle = doc.querySelector('title')?.textContent;\r\n if (newTitle) {\r\n document.title = newTitle;\r\n }\r\n \r\n // Update body content (or specific container)\r\n const newContent = doc.querySelector('#root') || doc.body;\r\n const currentContent = document.querySelector('#root') || document.body;\r\n \r\n if (newContent && currentContent) {\r\n currentContent.innerHTML = newContent.innerHTML;\r\n }\r\n \r\n // Update URL\r\n navigate(href, { replace, scroll });\r\n } else {\r\n // Fallback to regular navigation on error\r\n window.location.href = href;\r\n }\r\n } catch (error) {\r\n // Fallback to regular navigation on error\r\n window.location.href = href;\r\n } finally {\r\n setIsNavigating(false);\r\n onNavigationEnd?.();\r\n }\r\n },\r\n [href, replace, scroll, onClick, onNavigationStart, onNavigationEnd, props.target]\r\n );\r\n\r\n return (\r\n <a\r\n ref={linkRef}\r\n href={href}\r\n className={className}\r\n onClick={handleClick}\r\n onMouseEnter={handleMouseEnter}\r\n onFocus={handleFocus}\r\n data-prefetch={prefetch}\r\n data-navigating={isNavigating || undefined}\r\n {...props}\r\n >\r\n {showLoading && isNavigating ? (\r\n loadingComponent || (\r\n <span className=\"flexi-link-loading\">\r\n <span className=\"flexi-link-spinner\" />\r\n {children}\r\n </span>\r\n )\r\n ) : (\r\n children\r\n )}\r\n </a>\r\n );\r\n}\r\n\r\n/**\r\n * Programmatic navigation function\r\n * \r\n * @example\r\n * ```tsx\r\n * import { useRouter } from '@flexireact/core/client';\r\n * \r\n * function MyComponent() {\r\n * const router = useRouter();\r\n * \r\n * const handleClick = () => {\r\n * router.push('/dashboard');\r\n * };\r\n * \r\n * return <button onClick={handleClick}>Go to Dashboard</button>;\r\n * }\r\n * ```\r\n */\r\nexport function useRouter() {\r\n return {\r\n push(url: string, options?: { scroll?: boolean }) {\r\n navigate(url, { replace: false, scroll: options?.scroll ?? true });\r\n // Trigger page reload for now (full SPA navigation requires more work)\r\n window.location.href = url;\r\n },\r\n \r\n replace(url: string, options?: { scroll?: boolean }) {\r\n navigate(url, { replace: true, scroll: options?.scroll ?? true });\r\n window.location.href = url;\r\n },\r\n \r\n back() {\r\n window.history.back();\r\n },\r\n \r\n forward() {\r\n window.history.forward();\r\n },\r\n \r\n prefetch(url: string) {\r\n if (isInternalUrl(url)) {\r\n prefetchUrl(url);\r\n }\r\n },\r\n \r\n refresh() {\r\n window.location.reload();\r\n }\r\n };\r\n}\r\n\r\nexport default Link;\r\n"],"mappings":"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","names":["React","React","navigate","Link","prefetch"]}
|
|
1
|
+
{"version":3,"sources":["../../../core/client/hydration.ts","../../../core/client/navigation.ts","../../../core/client/islands.ts","../../../core/client/Link.tsx"],"sourcesContent":["/**\r\n * FlexiReact Client Hydration\r\n * Handles selective hydration of islands and full app hydration\r\n */\r\n\r\nimport React from 'react';\r\nimport { hydrateRoot, createRoot } from 'react-dom/client';\r\n\r\n// Extend Window interface for __FLEXI_DATA__\r\ndeclare global {\r\n interface Window {\r\n __FLEXI_DATA__?: {\r\n islands?: any[];\r\n props?: Record<string, any>;\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Hydrates a specific island component\r\n * React 19: Uses built-in hydration error recovery\r\n */\r\nexport function hydrateIsland(islandId, Component, props) {\r\n const element = document.querySelector(`[data-island=\"${islandId}\"]`);\r\n\r\n if (!element) {\r\n console.warn(`Island element not found: ${islandId}`);\r\n return;\r\n }\r\n\r\n if (element.hasAttribute('data-hydrated')) {\r\n return; // Already hydrated\r\n }\r\n\r\n // React 19: Built-in hydration error recovery\r\n hydrateRoot(element, React.createElement(Component, props), {\r\n onRecoverableError: (error, errorInfo) => {\r\n console.warn(`[FlexiReact] Hydration mismatch in ${islandId}:`, error);\r\n if (process.env.NODE_ENV === 'development') {\r\n console.debug('Component stack:', errorInfo.componentStack);\r\n }\r\n }\r\n });\r\n\r\n element.setAttribute('data-hydrated', 'true');\r\n\r\n // Dispatch custom event\r\n element.dispatchEvent(new CustomEvent('flexi:hydrated', {\r\n bubbles: true,\r\n detail: { islandId, props }\r\n }));\r\n}\r\n\r\n/**\r\n * Hydrates the entire application\r\n */\r\nexport function hydrateApp(App, props = {}) {\r\n const root = document.getElementById('root');\r\n\r\n if (!root) {\r\n console.error('Root element not found');\r\n return;\r\n }\r\n\r\n // Get server-rendered props\r\n const serverProps = window.__FLEXI_DATA__?.props || {};\r\n const mergedProps = { ...serverProps, ...props };\r\n\r\n try {\r\n hydrateRoot(root, React.createElement(App, mergedProps));\r\n } catch (error) {\r\n console.error('Hydration failed, falling back to full render:', error);\r\n createRoot(root).render(React.createElement(App, mergedProps));\r\n }\r\n}\r\n\r\n/**\r\n * Hydrates all islands on the page\r\n */\r\nexport async function hydrateAllIslands(islandModules) {\r\n const islands = document.querySelectorAll('[data-island]');\r\n\r\n for (const element of islands) {\r\n if (element.hasAttribute('data-hydrated')) continue;\r\n\r\n const islandId = element.getAttribute('data-island');\r\n const islandName = element.getAttribute('data-island-name');\r\n const propsJson = element.getAttribute('data-island-props');\r\n\r\n try {\r\n const props = propsJson ? JSON.parse(propsJson) : {};\r\n const module = islandModules[islandName];\r\n\r\n if (module) {\r\n hydrateIsland(islandId, module.default || module, props);\r\n }\r\n } catch (error) {\r\n console.error(`Failed to hydrate island ${islandName}:`, error);\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Progressive hydration based on visibility\r\n */\r\nexport function setupProgressiveHydration(islandModules) {\r\n const observer = new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach(async (entry) => {\r\n if (!entry.isIntersecting) return;\r\n\r\n const element = entry.target;\r\n if (element.hasAttribute('data-hydrated')) return;\r\n\r\n const islandName = element.getAttribute('data-island-name');\r\n const islandId = element.getAttribute('data-island');\r\n const propsJson = element.getAttribute('data-island-props');\r\n\r\n try {\r\n const props = propsJson ? JSON.parse(propsJson) : {};\r\n const module = await islandModules[islandName]();\r\n\r\n hydrateIsland(islandId, module.default || module, props);\r\n observer.unobserve(element);\r\n } catch (error) {\r\n console.error(`Failed to hydrate island ${islandName}:`, error);\r\n }\r\n });\r\n },\r\n { rootMargin: '50px' }\r\n );\r\n\r\n document.querySelectorAll('[data-island]:not([data-hydrated])').forEach(el => {\r\n observer.observe(el);\r\n });\r\n\r\n return observer;\r\n}\r\n\r\nexport default {\r\n hydrateIsland,\r\n hydrateApp,\r\n hydrateAllIslands,\r\n setupProgressiveHydration\r\n};\r\n","/**\r\n * FlexiReact Client Navigation\r\n * Client-side navigation with prefetching\r\n */\r\n\r\nimport React from 'react';\r\n\r\n// Navigation state\r\nconst navigationState: {\r\n listeners: Set<(url: string) => void>;\r\n prefetched: Set<string>;\r\n} = {\r\n listeners: new Set(),\r\n prefetched: new Set()\r\n};\r\n\r\n/**\r\n * Navigates to a new URL\r\n */\r\ninterface NavigateOptions {\r\n replace?: boolean;\r\n scroll?: boolean;\r\n}\r\n\r\nexport function navigate(url: string, options: NavigateOptions = {}) {\r\n const { replace = false, scroll = true } = options;\r\n\r\n if (replace) {\r\n window.history.replaceState({}, '', url);\r\n } else {\r\n window.history.pushState({}, '', url);\r\n }\r\n\r\n // Dispatch navigation event\r\n window.dispatchEvent(new CustomEvent('flexi:navigate', {\r\n detail: { url, replace, scroll }\r\n }));\r\n\r\n // Scroll to top if needed\r\n if (scroll) {\r\n window.scrollTo(0, 0);\r\n }\r\n\r\n // Notify listeners\r\n navigationState.listeners.forEach(listener => listener(url));\r\n\r\n // Fetch and render new page\r\n return fetchAndRender(url);\r\n}\r\n\r\n/**\r\n * Prefetches a URL for faster navigation\r\n */\r\nexport function prefetch(url) {\r\n if (navigationState.prefetched.has(url)) {\r\n return Promise.resolve();\r\n }\r\n\r\n navigationState.prefetched.add(url);\r\n\r\n // Create a link element for prefetching\r\n const link = document.createElement('link');\r\n link.rel = 'prefetch';\r\n link.href = url;\r\n document.head.appendChild(link);\r\n\r\n return Promise.resolve();\r\n}\r\n\r\n/**\r\n * Fetches and renders a new page\r\n */\r\nasync function fetchAndRender(url) {\r\n try {\r\n const response = await fetch(url, {\r\n headers: {\r\n 'X-Flexi-Navigation': 'true'\r\n }\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Navigation failed: ${response.status}`);\r\n }\r\n\r\n const html = await response.text();\r\n \r\n // Parse the HTML\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(html, 'text/html');\r\n\r\n // Update the page content\r\n const newRoot = doc.getElementById('root');\r\n const currentRoot = document.getElementById('root');\r\n\r\n if (newRoot && currentRoot) {\r\n currentRoot.innerHTML = newRoot.innerHTML;\r\n }\r\n\r\n // Update the title\r\n document.title = doc.title;\r\n\r\n // Update meta tags\r\n updateMetaTags(doc);\r\n\r\n // Re-hydrate islands\r\n window.dispatchEvent(new CustomEvent('flexi:pageload'));\r\n\r\n } catch (error) {\r\n console.error('Navigation error:', error);\r\n // Fallback to full page navigation\r\n window.location.href = url;\r\n }\r\n}\r\n\r\n/**\r\n * Updates meta tags from new document\r\n */\r\nfunction updateMetaTags(doc) {\r\n // Remove old meta tags\r\n document.querySelectorAll('meta[data-flexi]').forEach(el => el.remove());\r\n\r\n // Add new meta tags\r\n doc.querySelectorAll('meta').forEach(meta => {\r\n if (meta.name || meta.property) {\r\n const newMeta = meta.cloneNode(true);\r\n newMeta.setAttribute('data-flexi', 'true');\r\n document.head.appendChild(newMeta);\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Link component for client-side navigation\r\n */\r\nexport function Link({ href, children, prefetch: shouldPrefetch = true, replace = false, className, ...props }) {\r\n const handleClick = (e) => {\r\n // Allow normal navigation for external links or modified clicks\r\n if (\r\n e.ctrlKey ||\r\n e.metaKey ||\r\n e.shiftKey ||\r\n e.button !== 0 ||\r\n href.startsWith('http') ||\r\n href.startsWith('//')\r\n ) {\r\n return;\r\n }\r\n\r\n e.preventDefault();\r\n navigate(href, { replace });\r\n };\r\n\r\n const handleMouseEnter = () => {\r\n if (shouldPrefetch) {\r\n prefetch(href);\r\n }\r\n };\r\n\r\n return React.createElement('a', {\r\n href,\r\n onClick: handleClick,\r\n onMouseEnter: handleMouseEnter,\r\n className,\r\n ...props\r\n }, children);\r\n}\r\n\r\n/**\r\n * Hook to listen for navigation events\r\n */\r\nexport function useNavigation() {\r\n const [pathname, setPathname] = React.useState(\r\n typeof window !== 'undefined' ? window.location.pathname : '/'\r\n );\r\n\r\n React.useEffect(() => {\r\n const handleNavigation = (url) => {\r\n setPathname(new URL(url, window.location.origin).pathname);\r\n };\r\n\r\n navigationState.listeners.add(handleNavigation);\r\n\r\n const handlePopState = () => {\r\n setPathname(window.location.pathname);\r\n navigationState.listeners.forEach(listener => listener(window.location.pathname));\r\n };\r\n\r\n window.addEventListener('popstate', handlePopState);\r\n\r\n return () => {\r\n navigationState.listeners.delete(handleNavigation);\r\n window.removeEventListener('popstate', handlePopState);\r\n };\r\n }, []);\r\n\r\n return { pathname, navigate, prefetch };\r\n}\r\n\r\n// Setup popstate listener\r\nif (typeof window !== 'undefined') {\r\n window.addEventListener('popstate', () => {\r\n const url = window.location.pathname + window.location.search;\r\n navigationState.listeners.forEach(listener => listener(url));\r\n });\r\n}\r\n\r\nexport default {\r\n navigate,\r\n prefetch,\r\n Link,\r\n useNavigation\r\n};\r\n","/**\r\n * FlexiReact Client Islands\r\n * Client-side island utilities\r\n */\r\n\r\nimport React from 'react';\r\n\r\n/**\r\n * Hook to check if component is hydrated\r\n */\r\nexport function useIsland() {\r\n const [isHydrated, setIsHydrated] = React.useState(false);\r\n\r\n React.useEffect(() => {\r\n setIsHydrated(true);\r\n }, []);\r\n\r\n return { isHydrated };\r\n}\r\n\r\n/**\r\n * Island boundary component\r\n * Wraps interactive components for partial hydration\r\n */\r\nexport function IslandBoundary({ children, fallback = null, name = 'island' }) {\r\n const { isHydrated } = useIsland();\r\n\r\n // On server or before hydration, render children normally\r\n // The server will wrap this in the island marker\r\n if (!isHydrated && fallback) {\r\n return fallback;\r\n }\r\n\r\n return React.createElement('div', {\r\n 'data-island-boundary': name,\r\n children\r\n });\r\n}\r\n\r\n/**\r\n * Creates a lazy-loaded island\r\n */\r\ninterface LazyIslandOptions {\r\n fallback?: React.ReactNode;\r\n name?: string;\r\n}\r\n\r\nexport function createClientIsland(loader: () => Promise<any>, options: LazyIslandOptions = {}) {\r\n const { fallback = null, name = 'lazy-island' } = options;\r\n\r\n return function LazyIsland(props) {\r\n const [Component, setComponent] = React.useState(null);\r\n const [error, setError] = React.useState(null);\r\n\r\n React.useEffect(() => {\r\n loader()\r\n .then(mod => setComponent(() => mod.default || mod))\r\n .catch(err => setError(err));\r\n }, []);\r\n\r\n if (error) {\r\n return React.createElement('div', {\r\n className: 'island-error',\r\n children: `Failed to load ${name}`\r\n });\r\n }\r\n\r\n if (!Component) {\r\n return fallback || React.createElement('div', {\r\n className: 'island-loading',\r\n children: 'Loading...'\r\n });\r\n }\r\n\r\n return React.createElement(Component, props);\r\n };\r\n}\r\n\r\n/**\r\n * Island with interaction trigger\r\n * Only hydrates when user interacts\r\n */\r\nexport function InteractiveIsland({ children, trigger = 'click', fallback }) {\r\n const [shouldHydrate, setShouldHydrate] = React.useState(false);\r\n const ref = React.useRef(null);\r\n\r\n React.useEffect(() => {\r\n const element = ref.current;\r\n if (!element) return;\r\n\r\n const handleInteraction = () => {\r\n setShouldHydrate(true);\r\n };\r\n\r\n element.addEventListener(trigger, handleInteraction, { once: true });\r\n\r\n return () => {\r\n element.removeEventListener(trigger, handleInteraction);\r\n };\r\n }, [trigger]);\r\n\r\n if (!shouldHydrate) {\r\n return React.createElement('div', {\r\n ref,\r\n 'data-interactive-island': 'true',\r\n children: fallback || children\r\n });\r\n }\r\n\r\n return children;\r\n}\r\n\r\n/**\r\n * Media query island\r\n * Only hydrates when media query matches\r\n */\r\nexport function MediaIsland({ children, query, fallback }) {\r\n const [matches, setMatches] = React.useState(false);\r\n\r\n React.useEffect(() => {\r\n const mediaQuery = window.matchMedia(query);\r\n setMatches(mediaQuery.matches);\r\n\r\n const handler = (e) => setMatches(e.matches);\r\n mediaQuery.addEventListener('change', handler);\r\n\r\n return () => mediaQuery.removeEventListener('change', handler);\r\n }, [query]);\r\n\r\n if (!matches) {\r\n return fallback || null;\r\n }\r\n\r\n return children;\r\n}\r\n\r\nexport default {\r\n useIsland,\r\n IslandBoundary,\r\n createClientIsland,\r\n InteractiveIsland,\r\n MediaIsland\r\n};\r\n","'use client';\r\n\r\n/**\r\n * FlexiReact Link Component\r\n * Enhanced link with prefetching, client-side navigation, and loading states\r\n */\r\n\r\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\r\n\r\nexport interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\r\n /** The URL to navigate to */\r\n href: string;\r\n /** Prefetch the page on hover/visibility */\r\n prefetch?: boolean | 'hover' | 'viewport';\r\n /** Replace the current history entry instead of pushing */\r\n replace?: boolean;\r\n /** Scroll to top after navigation */\r\n scroll?: boolean;\r\n /** Show loading indicator while navigating */\r\n showLoading?: boolean;\r\n /** Custom loading component */\r\n loadingComponent?: React.ReactNode;\r\n /** Callback when navigation starts */\r\n onNavigationStart?: () => void;\r\n /** Callback when navigation ends */\r\n onNavigationEnd?: () => void;\r\n /** \r\n * Ref to the anchor element (React 19: ref as regular prop)\r\n * No forwardRef wrapper needed in React 19\r\n */\r\n ref?: React.Ref<HTMLAnchorElement>;\r\n /** Children */\r\n children: React.ReactNode;\r\n}\r\n\r\n// Prefetch cache to avoid duplicate requests\r\nconst prefetchCache = new Set<string>();\r\n\r\n// Prefetch a URL\r\nasync function prefetchUrl(url: string): Promise<void> {\r\n if (prefetchCache.has(url)) return;\r\n\r\n try {\r\n // Mark as prefetched immediately to prevent duplicate requests\r\n prefetchCache.add(url);\r\n\r\n // Use link preload for better browser optimization\r\n const link = document.createElement('link');\r\n link.rel = 'prefetch';\r\n link.href = url;\r\n link.as = 'document';\r\n document.head.appendChild(link);\r\n\r\n // Also fetch the page to warm the cache\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), 5000);\r\n\r\n await fetch(url, {\r\n method: 'GET',\r\n credentials: 'same-origin',\r\n signal: controller.signal,\r\n headers: {\r\n 'X-Flexi-Prefetch': '1',\r\n 'Accept': 'text/html'\r\n }\r\n });\r\n\r\n clearTimeout(timeoutId);\r\n } catch (error) {\r\n // Remove from cache on error so it can be retried\r\n prefetchCache.delete(url);\r\n }\r\n}\r\n\r\n// Check if URL is internal\r\nfunction isInternalUrl(url: string): boolean {\r\n if (url.startsWith('/')) return true;\r\n if (url.startsWith('#')) return true;\r\n\r\n try {\r\n const parsed = new URL(url, window.location.origin);\r\n return parsed.origin === window.location.origin;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n// Navigate to a URL\r\nfunction navigate(url: string, options: { replace?: boolean; scroll?: boolean } = {}): void {\r\n const { replace = false, scroll = true } = options;\r\n\r\n if (replace) {\r\n window.history.replaceState({}, '', url);\r\n } else {\r\n window.history.pushState({}, '', url);\r\n }\r\n\r\n // Dispatch popstate event to trigger any listeners\r\n window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));\r\n\r\n // Scroll to top if requested\r\n if (scroll) {\r\n window.scrollTo({ top: 0, behavior: 'smooth' });\r\n }\r\n}\r\n\r\n/**\r\n * Link component with prefetching and client-side navigation\r\n * \r\n * @example\r\n * ```tsx\r\n * import { Link } from '@flexireact/core/client';\r\n * \r\n * // Basic usage\r\n * <Link href=\"/about\">About</Link>\r\n * \r\n * // With prefetch on hover\r\n * <Link href=\"/products\" prefetch=\"hover\">Products</Link>\r\n * \r\n * // With prefetch on viewport visibility\r\n * <Link href=\"/contact\" prefetch=\"viewport\">Contact</Link>\r\n * \r\n * // Replace history instead of push\r\n * <Link href=\"/login\" replace>Login</Link>\r\n * \r\n * // Disable scroll to top\r\n * <Link href=\"/section#anchor\" scroll={false}>Go to section</Link>\r\n * ```\r\n */\r\nexport function Link({\r\n href,\r\n prefetch = true,\r\n replace = false,\r\n scroll = true,\r\n showLoading = false,\r\n loadingComponent,\r\n onNavigationStart,\r\n onNavigationEnd,\r\n children,\r\n className,\r\n onClick,\r\n onMouseEnter,\r\n onFocus,\r\n ...props\r\n}: LinkProps) {\r\n const [isNavigating, setIsNavigating] = useState(false);\r\n const linkRef = useRef<HTMLAnchorElement>(null);\r\n const hasPrefetched = useRef(false);\r\n\r\n // Prefetch on viewport visibility\r\n useEffect(() => {\r\n if (prefetch !== 'viewport' && prefetch !== true) return;\r\n if (!isInternalUrl(href)) return;\r\n if (hasPrefetched.current) return;\r\n\r\n const observer = new IntersectionObserver(\r\n (entries) => {\r\n entries.forEach((entry) => {\r\n if (entry.isIntersecting) {\r\n prefetchUrl(href);\r\n hasPrefetched.current = true;\r\n observer.disconnect();\r\n }\r\n });\r\n },\r\n { rootMargin: '200px' }\r\n );\r\n\r\n if (linkRef.current) {\r\n observer.observe(linkRef.current);\r\n }\r\n\r\n return () => observer.disconnect();\r\n }, [href, prefetch]);\r\n\r\n // Handle hover prefetch\r\n const handleMouseEnter = useCallback(\r\n (e: React.MouseEvent<HTMLAnchorElement>) => {\r\n onMouseEnter?.(e);\r\n\r\n if ((prefetch === 'hover' || prefetch === true) && isInternalUrl(href)) {\r\n prefetchUrl(href);\r\n }\r\n },\r\n [href, prefetch, onMouseEnter]\r\n );\r\n\r\n // Handle focus prefetch (for keyboard navigation)\r\n const handleFocus = useCallback(\r\n (e: React.FocusEvent<HTMLAnchorElement>) => {\r\n onFocus?.(e);\r\n\r\n if ((prefetch === 'hover' || prefetch === true) && isInternalUrl(href)) {\r\n prefetchUrl(href);\r\n }\r\n },\r\n [href, prefetch, onFocus]\r\n );\r\n\r\n // Handle click for client-side navigation\r\n const handleClick = useCallback(\r\n async (e: React.MouseEvent<HTMLAnchorElement>) => {\r\n onClick?.(e);\r\n\r\n // Don't handle if default was prevented\r\n if (e.defaultPrevented) return;\r\n\r\n // Don't handle if modifier keys are pressed (open in new tab, etc.)\r\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;\r\n\r\n // Don't handle external URLs\r\n if (!isInternalUrl(href)) return;\r\n\r\n // Don't handle if target is set\r\n if (props.target && props.target !== '_self') return;\r\n\r\n // Prevent default navigation\r\n e.preventDefault();\r\n\r\n // Start navigation\r\n setIsNavigating(true);\r\n onNavigationStart?.();\r\n\r\n try {\r\n // Fetch the new page\r\n const response = await fetch(href, {\r\n method: 'GET',\r\n credentials: 'same-origin',\r\n headers: {\r\n 'X-Flexi-Navigation': '1',\r\n 'Accept': 'text/html'\r\n }\r\n });\r\n\r\n if (response.ok) {\r\n const html = await response.text();\r\n\r\n // Parse and update the page\r\n const parser = new DOMParser();\r\n const doc = parser.parseFromString(html, 'text/html');\r\n\r\n // Update title\r\n const newTitle = doc.querySelector('title')?.textContent;\r\n if (newTitle) {\r\n document.title = newTitle;\r\n }\r\n\r\n // Update body content (or specific container)\r\n const newContent = doc.querySelector('#root') || doc.body;\r\n const currentContent = document.querySelector('#root') || document.body;\r\n\r\n if (newContent && currentContent) {\r\n currentContent.innerHTML = newContent.innerHTML;\r\n }\r\n\r\n // Update URL\r\n navigate(href, { replace, scroll });\r\n } else {\r\n // Fallback to regular navigation on error\r\n window.location.href = href;\r\n }\r\n } catch (error) {\r\n // Fallback to regular navigation on error\r\n window.location.href = href;\r\n } finally {\r\n setIsNavigating(false);\r\n onNavigationEnd?.();\r\n }\r\n },\r\n [href, replace, scroll, onClick, onNavigationStart, onNavigationEnd, props.target]\r\n );\r\n\r\n return (\r\n <a\r\n ref={linkRef}\r\n href={href}\r\n className={className}\r\n onClick={handleClick}\r\n onMouseEnter={handleMouseEnter}\r\n onFocus={handleFocus}\r\n data-prefetch={prefetch}\r\n data-navigating={isNavigating || undefined}\r\n {...props}\r\n >\r\n {showLoading && isNavigating ? (\r\n loadingComponent || (\r\n <span className=\"flexi-link-loading\">\r\n <span className=\"flexi-link-spinner\" />\r\n {children}\r\n </span>\r\n )\r\n ) : (\r\n children\r\n )}\r\n </a>\r\n );\r\n}\r\n\r\n/**\r\n * Programmatic navigation function\r\n * \r\n * @example\r\n * ```tsx\r\n * import { useRouter } from '@flexireact/core/client';\r\n * \r\n * function MyComponent() {\r\n * const router = useRouter();\r\n * \r\n * const handleClick = () => {\r\n * router.push('/dashboard');\r\n * };\r\n * \r\n * return <button onClick={handleClick}>Go to Dashboard</button>;\r\n * }\r\n * ```\r\n */\r\nexport function useRouter() {\r\n return {\r\n push(url: string, options?: { scroll?: boolean }) {\r\n navigate(url, { replace: false, scroll: options?.scroll ?? true });\r\n // Trigger page reload for now (full SPA navigation requires more work)\r\n window.location.href = url;\r\n },\r\n\r\n replace(url: string, options?: { scroll?: boolean }) {\r\n navigate(url, { replace: true, scroll: options?.scroll ?? true });\r\n window.location.href = url;\r\n },\r\n\r\n back() {\r\n window.history.back();\r\n },\r\n\r\n forward() {\r\n window.history.forward();\r\n },\r\n\r\n prefetch(url: string) {\r\n if (isInternalUrl(url)) {\r\n prefetchUrl(url);\r\n }\r\n },\r\n\r\n refresh() {\r\n window.location.reload();\r\n }\r\n };\r\n}\r\n\r\nexport default Link;\r\n"],"mappings":"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","names":["React","React","navigate","Link","prefetch"]}
|
package/dist/core/index.js
CHANGED
|
@@ -2911,6 +2911,9 @@ function isMethod(request, method) {
|
|
|
2911
2911
|
}
|
|
2912
2912
|
|
|
2913
2913
|
// core/actions/index.ts
|
|
2914
|
+
import { useActionState, useOptimistic } from "react";
|
|
2915
|
+
import { useFormStatus } from "react-dom";
|
|
2916
|
+
import { useActionState as useActionStateReact } from "react";
|
|
2914
2917
|
globalThis.__FLEXI_ACTIONS__ = globalThis.__FLEXI_ACTIONS__ || {};
|
|
2915
2918
|
globalThis.__FLEXI_ACTION_CONTEXT__ = null;
|
|
2916
2919
|
function serverAction(fn, actionId) {
|
|
@@ -3075,11 +3078,18 @@ function formAction(action) {
|
|
|
3075
3078
|
}
|
|
3076
3079
|
};
|
|
3077
3080
|
}
|
|
3081
|
+
function useFlexiAction(action, initialState, permalink) {
|
|
3082
|
+
return useActionStateReact(action, initialState, permalink);
|
|
3083
|
+
}
|
|
3078
3084
|
function createFormState(action, initialState = null) {
|
|
3085
|
+
if (typeof window !== "undefined" && process.env.NODE_ENV === "development") {
|
|
3086
|
+
console.warn(
|
|
3087
|
+
"[FlexiReact] createFormState is deprecated. Use useActionState from React 19 instead.\nSee migration guide: https://flexireact.dev/docs/react-19-migration"
|
|
3088
|
+
);
|
|
3089
|
+
}
|
|
3079
3090
|
return {
|
|
3080
3091
|
action,
|
|
3081
3092
|
initialState,
|
|
3082
|
-
// This will be enhanced on the client
|
|
3083
3093
|
pending: false,
|
|
3084
3094
|
error: null,
|
|
3085
3095
|
data: initialState
|
|
@@ -5749,8 +5759,34 @@ var jsonLd = {
|
|
|
5749
5759
|
})
|
|
5750
5760
|
};
|
|
5751
5761
|
|
|
5762
|
+
// core/index.ts
|
|
5763
|
+
import { useActionState as useActionState3, useOptimistic as useOptimistic3 } from "react";
|
|
5764
|
+
import { useFormStatus as useFormStatus3 } from "react-dom";
|
|
5765
|
+
|
|
5766
|
+
// core/hooks/index.ts
|
|
5767
|
+
import { useActionState as useActionState2, useOptimistic as useOptimistic2 } from "react";
|
|
5768
|
+
import { useFormStatus as useFormStatus2 } from "react-dom";
|
|
5769
|
+
import { use } from "react";
|
|
5770
|
+
|
|
5771
|
+
// core/client/Link.tsx
|
|
5772
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5773
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5774
|
+
|
|
5775
|
+
// core/hooks/index.ts
|
|
5776
|
+
import { use as use2, useOptimistic as useOptimisticReact } from "react";
|
|
5777
|
+
function useAsyncData(promise) {
|
|
5778
|
+
return use2(promise);
|
|
5779
|
+
}
|
|
5780
|
+
function useOptimisticMutation(currentState, updateFn) {
|
|
5781
|
+
return useOptimisticReact(currentState, updateFn);
|
|
5782
|
+
}
|
|
5783
|
+
function preloadResource(fetcher) {
|
|
5784
|
+
const promise = fetcher();
|
|
5785
|
+
return promise;
|
|
5786
|
+
}
|
|
5787
|
+
|
|
5752
5788
|
// core/devtools/index.ts
|
|
5753
|
-
import
|
|
5789
|
+
import React8 from "react";
|
|
5754
5790
|
var devToolsState = {
|
|
5755
5791
|
routes: [],
|
|
5756
5792
|
components: /* @__PURE__ */ new Map(),
|
|
@@ -5928,24 +5964,24 @@ function initNetworkInterceptor() {
|
|
|
5928
5964
|
};
|
|
5929
5965
|
}
|
|
5930
5966
|
function DevToolsOverlay() {
|
|
5931
|
-
const [state, setState] =
|
|
5967
|
+
const [state, setState] = React8.useState({
|
|
5932
5968
|
enabled: true,
|
|
5933
5969
|
position: "bottom-right",
|
|
5934
5970
|
expanded: false,
|
|
5935
5971
|
activeTab: "routes",
|
|
5936
5972
|
theme: "dark"
|
|
5937
5973
|
});
|
|
5938
|
-
const [data, setData] =
|
|
5939
|
-
|
|
5974
|
+
const [data, setData] = React8.useState(devtools.getState());
|
|
5975
|
+
React8.useEffect(() => {
|
|
5940
5976
|
return devtools.subscribe(() => {
|
|
5941
5977
|
setData(devtools.getState());
|
|
5942
5978
|
});
|
|
5943
5979
|
}, []);
|
|
5944
|
-
|
|
5980
|
+
React8.useEffect(() => {
|
|
5945
5981
|
initPerformanceMonitoring();
|
|
5946
5982
|
initNetworkInterceptor();
|
|
5947
5983
|
}, []);
|
|
5948
|
-
|
|
5984
|
+
React8.useEffect(() => {
|
|
5949
5985
|
const handler = (e) => {
|
|
5950
5986
|
if (e.ctrlKey && e.shiftKey && e.key === "D") {
|
|
5951
5987
|
setState((s) => ({ ...s, expanded: !s.expanded }));
|
|
@@ -5962,7 +5998,7 @@ function DevToolsOverlay() {
|
|
|
5962
5998
|
"top-left": { top: 16, left: 16 }
|
|
5963
5999
|
};
|
|
5964
6000
|
if (!state.expanded) {
|
|
5965
|
-
return
|
|
6001
|
+
return React8.createElement("button", {
|
|
5966
6002
|
onClick: () => setState((s) => ({ ...s, expanded: true })),
|
|
5967
6003
|
style: {
|
|
5968
6004
|
position: "fixed",
|
|
@@ -5983,7 +6019,7 @@ function DevToolsOverlay() {
|
|
|
5983
6019
|
onMouseEnter: (e) => e.target.style.transform = "scale(1.1)",
|
|
5984
6020
|
onMouseLeave: (e) => e.target.style.transform = "scale(1)",
|
|
5985
6021
|
title: "FlexiReact DevTools (Ctrl+Shift+D)"
|
|
5986
|
-
},
|
|
6022
|
+
}, React8.createElement("span", { style: { fontSize: 24 } }, "\u26A1"));
|
|
5987
6023
|
}
|
|
5988
6024
|
const tabs = [
|
|
5989
6025
|
{ id: "routes", label: "\u{1F5FA}\uFE0F Routes", count: data.routes.length },
|
|
@@ -5992,7 +6028,7 @@ function DevToolsOverlay() {
|
|
|
5992
6028
|
{ id: "performance", label: "\u{1F4CA} Performance", count: data.performance.length },
|
|
5993
6029
|
{ id: "console", label: "\u{1F4DD} Console", count: data.logs.length }
|
|
5994
6030
|
];
|
|
5995
|
-
return
|
|
6031
|
+
return React8.createElement("div", {
|
|
5996
6032
|
style: {
|
|
5997
6033
|
position: "fixed",
|
|
5998
6034
|
...positionStyles[state.position],
|
|
@@ -6010,7 +6046,7 @@ function DevToolsOverlay() {
|
|
|
6010
6046
|
}
|
|
6011
6047
|
}, [
|
|
6012
6048
|
// Header
|
|
6013
|
-
|
|
6049
|
+
React8.createElement("div", {
|
|
6014
6050
|
key: "header",
|
|
6015
6051
|
style: {
|
|
6016
6052
|
display: "flex",
|
|
@@ -6021,14 +6057,14 @@ function DevToolsOverlay() {
|
|
|
6021
6057
|
background: "#111"
|
|
6022
6058
|
}
|
|
6023
6059
|
}, [
|
|
6024
|
-
|
|
6060
|
+
React8.createElement("div", {
|
|
6025
6061
|
key: "title",
|
|
6026
6062
|
style: { display: "flex", alignItems: "center", gap: 8 }
|
|
6027
6063
|
}, [
|
|
6028
|
-
|
|
6029
|
-
|
|
6064
|
+
React8.createElement("span", { key: "icon" }, "\u26A1"),
|
|
6065
|
+
React8.createElement("span", { key: "text", style: { fontWeight: 600 } }, "FlexiReact DevTools")
|
|
6030
6066
|
]),
|
|
6031
|
-
|
|
6067
|
+
React8.createElement("button", {
|
|
6032
6068
|
key: "close",
|
|
6033
6069
|
onClick: () => setState((s) => ({ ...s, expanded: false })),
|
|
6034
6070
|
style: {
|
|
@@ -6041,7 +6077,7 @@ function DevToolsOverlay() {
|
|
|
6041
6077
|
}, "\xD7")
|
|
6042
6078
|
]),
|
|
6043
6079
|
// Tabs
|
|
6044
|
-
|
|
6080
|
+
React8.createElement("div", {
|
|
6045
6081
|
key: "tabs",
|
|
6046
6082
|
style: {
|
|
6047
6083
|
display: "flex",
|
|
@@ -6050,7 +6086,7 @@ function DevToolsOverlay() {
|
|
|
6050
6086
|
overflowX: "auto"
|
|
6051
6087
|
}
|
|
6052
6088
|
}, tabs.map(
|
|
6053
|
-
(tab) =>
|
|
6089
|
+
(tab) => React8.createElement("button", {
|
|
6054
6090
|
key: tab.id,
|
|
6055
6091
|
onClick: () => setState((s) => ({ ...s, activeTab: tab.id })),
|
|
6056
6092
|
style: {
|
|
@@ -6066,7 +6102,7 @@ function DevToolsOverlay() {
|
|
|
6066
6102
|
}, `${tab.label} (${tab.count})`)
|
|
6067
6103
|
)),
|
|
6068
6104
|
// Content
|
|
6069
|
-
|
|
6105
|
+
React8.createElement("div", {
|
|
6070
6106
|
key: "content",
|
|
6071
6107
|
style: {
|
|
6072
6108
|
padding: 16,
|
|
@@ -6079,11 +6115,11 @@ function DevToolsOverlay() {
|
|
|
6079
6115
|
function renderTabContent(tab, data) {
|
|
6080
6116
|
switch (tab) {
|
|
6081
6117
|
case "routes":
|
|
6082
|
-
return
|
|
6118
|
+
return React8.createElement(
|
|
6083
6119
|
"div",
|
|
6084
6120
|
{ style: { display: "flex", flexDirection: "column", gap: 8 } },
|
|
6085
|
-
data.routes.length === 0 ?
|
|
6086
|
-
(route, i) =>
|
|
6121
|
+
data.routes.length === 0 ? React8.createElement("div", { style: { color: "#666", textAlign: "center", padding: 20 } }, "No routes tracked yet") : data.routes.map(
|
|
6122
|
+
(route, i) => React8.createElement("div", {
|
|
6087
6123
|
key: i,
|
|
6088
6124
|
style: {
|
|
6089
6125
|
padding: 12,
|
|
@@ -6092,8 +6128,8 @@ function renderTabContent(tab, data) {
|
|
|
6092
6128
|
border: "1px solid #222"
|
|
6093
6129
|
}
|
|
6094
6130
|
}, [
|
|
6095
|
-
|
|
6096
|
-
|
|
6131
|
+
React8.createElement("div", { key: "path", style: { fontWeight: 600, color: "#00FF9C" } }, route.path),
|
|
6132
|
+
React8.createElement(
|
|
6097
6133
|
"div",
|
|
6098
6134
|
{ key: "component", style: { fontSize: 11, color: "#888", marginTop: 4 } },
|
|
6099
6135
|
`Component: ${route.component} \u2022 ${route.loadTime}ms`
|
|
@@ -6102,11 +6138,11 @@ function renderTabContent(tab, data) {
|
|
|
6102
6138
|
)
|
|
6103
6139
|
);
|
|
6104
6140
|
case "components":
|
|
6105
|
-
return
|
|
6141
|
+
return React8.createElement(
|
|
6106
6142
|
"div",
|
|
6107
6143
|
{ style: { display: "flex", flexDirection: "column", gap: 8 } },
|
|
6108
|
-
data.components.length === 0 ?
|
|
6109
|
-
(comp, i) =>
|
|
6144
|
+
data.components.length === 0 ? React8.createElement("div", { style: { color: "#666", textAlign: "center", padding: 20 } }, "No components tracked") : data.components.map(
|
|
6145
|
+
(comp, i) => React8.createElement("div", {
|
|
6110
6146
|
key: i,
|
|
6111
6147
|
style: {
|
|
6112
6148
|
padding: 12,
|
|
@@ -6115,12 +6151,12 @@ function renderTabContent(tab, data) {
|
|
|
6115
6151
|
border: "1px solid #222"
|
|
6116
6152
|
}
|
|
6117
6153
|
}, [
|
|
6118
|
-
|
|
6154
|
+
React8.createElement("div", {
|
|
6119
6155
|
key: "name",
|
|
6120
6156
|
style: { display: "flex", alignItems: "center", gap: 8 }
|
|
6121
6157
|
}, [
|
|
6122
|
-
|
|
6123
|
-
comp.isIsland &&
|
|
6158
|
+
React8.createElement("span", { key: "text", style: { fontWeight: 600 } }, comp.name),
|
|
6159
|
+
comp.isIsland && React8.createElement("span", {
|
|
6124
6160
|
key: "island",
|
|
6125
6161
|
style: {
|
|
6126
6162
|
fontSize: 10,
|
|
@@ -6131,7 +6167,7 @@ function renderTabContent(tab, data) {
|
|
|
6131
6167
|
}
|
|
6132
6168
|
}, "Island")
|
|
6133
6169
|
]),
|
|
6134
|
-
|
|
6170
|
+
React8.createElement(
|
|
6135
6171
|
"div",
|
|
6136
6172
|
{ key: "info", style: { fontSize: 11, color: "#888", marginTop: 4 } },
|
|
6137
6173
|
`Renders: ${comp.renderCount} \u2022 Last: ${new Date(comp.lastRenderTime).toLocaleTimeString()}`
|
|
@@ -6140,11 +6176,11 @@ function renderTabContent(tab, data) {
|
|
|
6140
6176
|
)
|
|
6141
6177
|
);
|
|
6142
6178
|
case "network":
|
|
6143
|
-
return
|
|
6179
|
+
return React8.createElement(
|
|
6144
6180
|
"div",
|
|
6145
6181
|
{ style: { display: "flex", flexDirection: "column", gap: 8 } },
|
|
6146
|
-
data.network.length === 0 ?
|
|
6147
|
-
(req, i) =>
|
|
6182
|
+
data.network.length === 0 ? React8.createElement("div", { style: { color: "#666", textAlign: "center", padding: 20 } }, "No requests yet") : data.network.map(
|
|
6183
|
+
(req, i) => React8.createElement("div", {
|
|
6148
6184
|
key: i,
|
|
6149
6185
|
style: {
|
|
6150
6186
|
padding: 12,
|
|
@@ -6153,11 +6189,11 @@ function renderTabContent(tab, data) {
|
|
|
6153
6189
|
border: "1px solid #222"
|
|
6154
6190
|
}
|
|
6155
6191
|
}, [
|
|
6156
|
-
|
|
6192
|
+
React8.createElement("div", {
|
|
6157
6193
|
key: "url",
|
|
6158
6194
|
style: { display: "flex", alignItems: "center", gap: 8 }
|
|
6159
6195
|
}, [
|
|
6160
|
-
|
|
6196
|
+
React8.createElement("span", {
|
|
6161
6197
|
key: "method",
|
|
6162
6198
|
style: {
|
|
6163
6199
|
fontSize: 10,
|
|
@@ -6168,7 +6204,7 @@ function renderTabContent(tab, data) {
|
|
|
6168
6204
|
fontWeight: 600
|
|
6169
6205
|
}
|
|
6170
6206
|
}, req.method),
|
|
6171
|
-
|
|
6207
|
+
React8.createElement("span", {
|
|
6172
6208
|
key: "status",
|
|
6173
6209
|
style: {
|
|
6174
6210
|
fontSize: 10,
|
|
@@ -6178,12 +6214,12 @@ function renderTabContent(tab, data) {
|
|
|
6178
6214
|
borderRadius: 4
|
|
6179
6215
|
}
|
|
6180
6216
|
}, req.status || "ERR"),
|
|
6181
|
-
|
|
6217
|
+
React8.createElement("span", {
|
|
6182
6218
|
key: "path",
|
|
6183
6219
|
style: { fontSize: 12, color: "#fff", overflow: "hidden", textOverflow: "ellipsis" }
|
|
6184
6220
|
}, new URL(req.url, "http://localhost").pathname)
|
|
6185
6221
|
]),
|
|
6186
|
-
|
|
6222
|
+
React8.createElement(
|
|
6187
6223
|
"div",
|
|
6188
6224
|
{ key: "info", style: { fontSize: 11, color: "#888", marginTop: 4 } },
|
|
6189
6225
|
`${req.duration}ms \u2022 ${formatBytes2(req.size)}`
|
|
@@ -6192,11 +6228,11 @@ function renderTabContent(tab, data) {
|
|
|
6192
6228
|
)
|
|
6193
6229
|
);
|
|
6194
6230
|
case "performance":
|
|
6195
|
-
return
|
|
6231
|
+
return React8.createElement(
|
|
6196
6232
|
"div",
|
|
6197
6233
|
{ style: { display: "flex", flexDirection: "column", gap: 8 } },
|
|
6198
|
-
data.performance.length === 0 ?
|
|
6199
|
-
(metric, i) =>
|
|
6234
|
+
data.performance.length === 0 ? React8.createElement("div", { style: { color: "#666", textAlign: "center", padding: 20 } }, "Collecting metrics...") : data.performance.map(
|
|
6235
|
+
(metric, i) => React8.createElement("div", {
|
|
6200
6236
|
key: i,
|
|
6201
6237
|
style: {
|
|
6202
6238
|
padding: 12,
|
|
@@ -6208,14 +6244,14 @@ function renderTabContent(tab, data) {
|
|
|
6208
6244
|
alignItems: "center"
|
|
6209
6245
|
}
|
|
6210
6246
|
}, [
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6247
|
+
React8.createElement("span", { key: "name", style: { fontWeight: 600 } }, metric.name),
|
|
6248
|
+
React8.createElement("div", { key: "value", style: { display: "flex", alignItems: "center", gap: 8 } }, [
|
|
6249
|
+
React8.createElement(
|
|
6214
6250
|
"span",
|
|
6215
6251
|
{ key: "num" },
|
|
6216
6252
|
metric.name === "CLS" ? metric.value.toFixed(3) : `${Math.round(metric.value)}ms`
|
|
6217
6253
|
),
|
|
6218
|
-
|
|
6254
|
+
React8.createElement("span", {
|
|
6219
6255
|
key: "rating",
|
|
6220
6256
|
style: {
|
|
6221
6257
|
width: 8,
|
|
@@ -6229,11 +6265,11 @@ function renderTabContent(tab, data) {
|
|
|
6229
6265
|
)
|
|
6230
6266
|
);
|
|
6231
6267
|
case "console":
|
|
6232
|
-
return
|
|
6268
|
+
return React8.createElement(
|
|
6233
6269
|
"div",
|
|
6234
6270
|
{ style: { display: "flex", flexDirection: "column", gap: 4 } },
|
|
6235
|
-
data.logs.length === 0 ?
|
|
6236
|
-
(log, i) =>
|
|
6271
|
+
data.logs.length === 0 ? React8.createElement("div", { style: { color: "#666", textAlign: "center", padding: 20 } }, "No logs yet") : data.logs.map(
|
|
6272
|
+
(log, i) => React8.createElement("div", {
|
|
6237
6273
|
key: i,
|
|
6238
6274
|
style: {
|
|
6239
6275
|
padding: "8px 12px",
|
|
@@ -6244,7 +6280,7 @@ function renderTabContent(tab, data) {
|
|
|
6244
6280
|
color: log.level === "error" ? "#EF4444" : log.level === "warn" ? "#F59E0B" : "#888"
|
|
6245
6281
|
}
|
|
6246
6282
|
}, [
|
|
6247
|
-
|
|
6283
|
+
React8.createElement(
|
|
6248
6284
|
"span",
|
|
6249
6285
|
{ key: "time", style: { color: "#444", marginRight: 8 } },
|
|
6250
6286
|
new Date(log.timestamp).toLocaleTimeString()
|
|
@@ -6254,7 +6290,7 @@ function renderTabContent(tab, data) {
|
|
|
6254
6290
|
)
|
|
6255
6291
|
);
|
|
6256
6292
|
default:
|
|
6257
|
-
return
|
|
6293
|
+
return React8.createElement("div", {}, "Unknown tab");
|
|
6258
6294
|
}
|
|
6259
6295
|
}
|
|
6260
6296
|
function formatBytes2(bytes) {
|
|
@@ -6266,7 +6302,7 @@ function formatBytes2(bytes) {
|
|
|
6266
6302
|
}
|
|
6267
6303
|
|
|
6268
6304
|
// core/index.ts
|
|
6269
|
-
var VERSION = "3.
|
|
6305
|
+
var VERSION = "3.1.0";
|
|
6270
6306
|
var core_default = {
|
|
6271
6307
|
VERSION,
|
|
6272
6308
|
createServer
|
|
@@ -6384,6 +6420,7 @@ export {
|
|
|
6384
6420
|
parseSearchParams,
|
|
6385
6421
|
pluginManager,
|
|
6386
6422
|
pprFetch,
|
|
6423
|
+
preloadResource,
|
|
6387
6424
|
prerenderWithPPR,
|
|
6388
6425
|
processServerComponent,
|
|
6389
6426
|
reactCache,
|
|
@@ -6407,6 +6444,12 @@ export {
|
|
|
6407
6444
|
text,
|
|
6408
6445
|
unstable_cache,
|
|
6409
6446
|
useActionContext,
|
|
6447
|
+
useActionState3 as useActionState,
|
|
6448
|
+
useAsyncData,
|
|
6449
|
+
useFlexiAction,
|
|
6450
|
+
useFormStatus3 as useFormStatus,
|
|
6451
|
+
useOptimistic3 as useOptimistic,
|
|
6452
|
+
useOptimisticMutation,
|
|
6410
6453
|
useParams,
|
|
6411
6454
|
usePathname,
|
|
6412
6455
|
useQuery,
|