@useavalon/avalon 0.1.47 → 0.1.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/mod.d.ts +8 -9
  2. package/dist/mod.js +1 -1
  3. package/dist/src/client/components.d.ts +8 -16
  4. package/dist/src/client/components.js +1 -1
  5. package/dist/src/client/custom-directives.d.ts +25 -0
  6. package/dist/src/client/custom-directives.js +1 -0
  7. package/dist/src/client/main.js +3 -3
  8. package/dist/src/components/IslandErrorBoundary.d.ts +48 -9
  9. package/dist/src/components/IslandErrorBoundary.js +1 -1
  10. package/dist/src/components/LayoutErrorBoundary.d.ts +74 -9
  11. package/dist/src/components/LayoutErrorBoundary.js +1 -1
  12. package/dist/src/islands/builtin-directives.d.ts +15 -0
  13. package/dist/src/islands/builtin-directives.js +1 -0
  14. package/dist/src/islands/hydration-directives.d.ts +89 -0
  15. package/dist/src/islands/hydration-directives.js +1 -0
  16. package/dist/src/islands/island.d.ts +5 -3
  17. package/dist/src/islands/island.js +1 -1
  18. package/dist/src/islands/types.d.ts +4 -2
  19. package/dist/src/layout-system.d.ts +0 -13
  20. package/dist/src/layout-system.js +1 -1
  21. package/dist/src/nitro/config.d.ts +3 -3
  22. package/dist/src/nitro/renderer.d.ts +14 -1
  23. package/dist/src/nitro/renderer.js +6 -6
  24. package/dist/src/persistence/island-state-serializer.d.ts +19 -0
  25. package/dist/src/persistence/island-state-serializer.js +1 -0
  26. package/dist/src/persistence/use-persistent-state.d.ts +31 -0
  27. package/dist/src/persistence/use-persistent-state.js +1 -0
  28. package/dist/src/prerender/index.d.ts +1 -1
  29. package/dist/src/prerender/prerender.d.ts +1 -1
  30. package/dist/src/prerender/prerender.js +1 -1
  31. package/dist/src/schemas/layout.d.ts +1 -1
  32. package/dist/src/schemas/layout.js +1 -1
  33. package/dist/src/types/island-prop.d.ts +4 -2
  34. package/dist/src/types/layout.d.ts +0 -8
  35. package/dist/src/types/layout.js +1 -1
  36. package/dist/src/vite-plugin/nitro-integration.d.ts +3 -3
  37. package/dist/src/vite-plugin/nitro-integration.js +14 -14
  38. package/package.json +2 -2
  39. package/dist/src/components/LayoutDataErrorBoundary.d.ts +0 -34
  40. package/dist/src/components/LayoutDataErrorBoundary.js +0 -1
  41. package/dist/src/components/PersistentIsland.d.ts +0 -36
  42. package/dist/src/components/PersistentIsland.js +0 -1
  43. package/dist/src/components/StreamingErrorBoundary.d.ts +0 -42
  44. package/dist/src/components/StreamingErrorBoundary.js +0 -1
  45. package/dist/src/components/StreamingLayout.d.ts +0 -83
  46. package/dist/src/components/StreamingLayout.js +0 -29
  47. package/dist/src/core/islands/island-persistence.d.ts +0 -74
  48. package/dist/src/core/islands/island-persistence.js +0 -1
  49. package/dist/src/core/islands/island-state-serializer.d.ts +0 -53
  50. package/dist/src/core/islands/island-state-serializer.js +0 -1
  51. package/dist/src/core/islands/persistent-island-context.d.ts +0 -36
  52. package/dist/src/core/islands/persistent-island-context.js +0 -1
  53. package/dist/src/core/islands/use-persistent-state.d.ts +0 -17
  54. package/dist/src/core/islands/use-persistent-state.js +0 -1
package/dist/mod.d.ts CHANGED
@@ -8,6 +8,9 @@ export { addSvelteSSRCSS, getSvelteSSRCSS, getSvelteSSRCSSForHead, getSvelteSSRC
8
8
  export { detectFramework, detectFrameworkFromSrc, resolveIslandPath } from './src/islands/framework-detection.ts';
9
9
  export { analyzeComponentFile, renderComponentSSROnly } from './src/islands/component-analysis.ts';
10
10
  export type { Framework, RenderParams, SvelteSSRCSSEntry } from './src/islands/types.ts';
11
+ export { registerHydrationDirective, unregisterHydrationDirective, isCustomDirective, getDirective, getRegisteredDirectives, } from './src/islands/hydration-directives.ts';
12
+ export type { HydrationDirectiveFn, HydrationDirectiveDefinition, } from './src/islands/hydration-directives.ts';
13
+ export { registerBuiltinDirectives } from './src/islands/builtin-directives.ts';
11
14
  export { clearCache, clearIslandCache, invalidateCacheForPath, invalidateCacheForFile, getCacheStats, logCacheStats, configureCache, getCacheConfig, } from './src/islands/render-cache.ts';
12
15
  export type { CacheConfig as IslandCacheConfig, CacheStats as IslandCacheStats } from './src/islands/render-cache.ts';
13
16
  export { discoverIslandDirectories, discoverIslandsInDirectory, discoverAllIslands, isIslandsDirectory, getDefaultIslandsPath, hasDefaultIslandsDirectory, getQualifiedIslandName, parseQualifiedIslandName, IslandRegistry, createIslandRegistry, IslandResolver, createIslandResolver, IslandValidator, createIslandValidator, validateAllIslands, formatValidationError, formatValidationWarning, formatCircularDependency, formatValidationResult, IslandWatcher, createIslandWatcher, ISLAND_FILE_EXTENSIONS, DEFAULT_DISCOVERY_CONFIG, isSupportedIslandExtension, } from './src/islands/discovery/index.ts';
@@ -38,13 +41,9 @@ export type { EnhancedLayoutResolverOptions } from './src/core/layout/enhanced-l
38
41
  export type { CacheEntry, CacheStats, CacheConfig } from './src/core/layout/layout-cache-manager.ts';
39
42
  export type { LayoutProps, LayoutContext, LayoutData, LayoutRoute, LayoutHandler, LayoutDiscoveryOptions, LayoutConfig, RouteInfo, LayoutRule, LayoutLoader, ResolvedLayout, LayoutCache, LayoutErrorInfo } from './src/core/layout/layout-types.ts';
40
43
  export type { EnhancedLayoutContext, IslandState, PersistentIslandProps, PersistentIslandContext, IslandStateSaver, IslandStateLoader, IslandStateClearer, LayoutErrorBoundaryProps, ErrorRecoveryStrategy, LayoutErrorHandler, LayoutRetryFunction, LayoutFallbackRenderer, StreamingLayoutProps, StreamingComponent, StreamingReadyCheck, LayoutMatcherFunction, } from './src/schemas/layout.ts';
41
- export { IslandPersistence, defaultIslandPersistence } from './src/core/islands/island-persistence.ts';
42
- export { IslandStateSerializer } from './src/core/islands/island-state-serializer.ts';
43
- export { createPersistentIslandContext, usePersistentIslandContext, PersistentIslandProvider, } from './src/core/islands/persistent-island-context.tsx';
44
- export { PersistentIsland } from './src/components/PersistentIsland.tsx';
45
- export { usePersistentState } from './src/core/islands/use-persistent-state.ts';
46
- export { LayoutErrorBoundary } from './src/components/LayoutErrorBoundary.tsx';
47
- export { LayoutDataErrorBoundary } from './src/components/LayoutDataErrorBoundary.tsx';
48
- export { IslandErrorBoundary, withIslandErrorBoundary } from './src/components/IslandErrorBoundary.tsx';
49
- export { StreamingErrorBoundary, withStreamingErrorBoundary } from './src/components/StreamingErrorBoundary.tsx';
50
44
  export type { ILayoutDiscovery, ILayoutMatcher, ILayoutComposer, IIslandPersistence, ILayoutErrorRecovery, ILayoutStreaming, IEnhancedLayoutResolver, ILayoutComponent, IPersistentIslandComponent, ILayoutErrorBoundaryComponent, IStreamingLayoutComponent, LayoutModule, PageModule, LayoutResolutionContext, LayoutPerformanceMetrics, LayoutDebugInfo, LayoutEventType, LayoutEventData, LayoutEventHandler, ILayoutEventEmitter, } from './src/types/layout.ts';
45
+ export { IslandErrorBoundary, withIslandErrorBoundary } from './src/components/IslandErrorBoundary.tsx';
46
+ export type { IslandErrorBoundaryProps } from './src/components/IslandErrorBoundary.tsx';
47
+ export { LayoutErrorBoundary } from './src/components/LayoutErrorBoundary.tsx';
48
+ export type { LayoutErrorBoundaryProps as LayoutErrorBoundaryComponentProps } from './src/components/LayoutErrorBoundary.tsx';
49
+ export { usePersistentState } from './src/persistence/use-persistent-state.ts';
package/dist/mod.js CHANGED
@@ -1 +1 @@
1
- export{avalon,getResolvedConfig,getPagesDir,getLayoutsDir,getNitroConfig,isNitroEnabled}from"./src/vite-plugin/plugin.js";export{createNitroIntegration,createNitroCoordinationPlugin,createVirtualModulesPlugin,getViteDevServer,getAvalonConfig,isDevelopmentMode,VIRTUAL_MODULE_IDS,RESOLVED_VIRTUAL_IDS}from"./src/vite-plugin/nitro-integration.js";export{renderToHtml}from"./src/render/ssr.js";export{default as Island,renderIsland}from"./src/islands/island.js";export{addSvelteSSRCSS,getSvelteSSRCSS,getSvelteSSRCSSForHead,getSvelteSSRCSSStats,getSvelteComponentCSS,clearSvelteComponentCSS,generateComponentScopeId}from"./src/islands/css-utils.js";export{detectFramework,detectFrameworkFromSrc,resolveIslandPath}from"./src/islands/framework-detection.js";export{analyzeComponentFile,renderComponentSSROnly}from"./src/islands/component-analysis.js";export{clearCache,clearIslandCache,invalidateCacheForPath,invalidateCacheForFile,getCacheStats,logCacheStats,configureCache,getCacheConfig}from"./src/islands/render-cache.js";export{discoverIslandDirectories,discoverIslandsInDirectory,discoverAllIslands,isIslandsDirectory,getDefaultIslandsPath,hasDefaultIslandsDirectory,getQualifiedIslandName,parseQualifiedIslandName,IslandRegistry,createIslandRegistry,IslandResolver,createIslandResolver,IslandValidator,createIslandValidator,validateAllIslands,formatValidationError,formatValidationWarning,formatCircularDependency,formatValidationResult,IslandWatcher,createIslandWatcher,ISLAND_FILE_EXTENSIONS,DEFAULT_DISCOVERY_CONFIG,isSupportedIslandExtension}from"./src/islands/discovery/index.js";export{loadIntegration,detectAndLoadIntegration,preloadIntegrations,detectFrameworksFromPageContent,DEFAULT_PRELOAD_FRAMEWORKS}from"./src/islands/integration-loader.js";export{registry as integrationRegistry}from"./src/core/integrations/registry.js";export{generateIslandManifest,loadIslandManifest,getIslandBundlePath}from"./src/build/island-manifest.js";export{mdxIslandTransform}from"./src/build/mdx-island-transform.js";export{pageIslandTransform}from"./src/build/page-island-transform.js";export{asIsland}from"./src/types/as-island.js";export{generateIslandTypes,watchAndGenerateTypes}from"./src/build/island-types-generator.js";export async function build(e){throw Error("avalon build() is not available in the published package. Use `vite build` or the Avalon CLI instead.")}export{discoverScopedMiddleware,executeScopedMiddleware,clearMiddlewareCache,invalidateMiddleware,getMatchingMiddleware,clearDiscoveryCache,hasContextValue,getContextValue,setContextValue,getMiddlewareCacheSize}from"./src/middleware/index.js";export*from"./src/layout-system.js";export{IslandPersistence,defaultIslandPersistence}from"./src/core/islands/island-persistence.js";export{IslandStateSerializer}from"./src/core/islands/island-state-serializer.js";export{createPersistentIslandContext,usePersistentIslandContext,PersistentIslandProvider}from"./src/core/islands/persistent-island-context.js";export{PersistentIsland}from"./src/components/PersistentIsland.js";export{usePersistentState}from"./src/core/islands/use-persistent-state.js";export{LayoutErrorBoundary}from"./src/components/LayoutErrorBoundary.js";export{LayoutDataErrorBoundary}from"./src/components/LayoutDataErrorBoundary.js";export{IslandErrorBoundary,withIslandErrorBoundary}from"./src/components/IslandErrorBoundary.js";export{StreamingErrorBoundary,withStreamingErrorBoundary}from"./src/components/StreamingErrorBoundary.js";
1
+ export{avalon,getResolvedConfig,getPagesDir,getLayoutsDir,getNitroConfig,isNitroEnabled}from"./src/vite-plugin/plugin.js";export{createNitroIntegration,createNitroCoordinationPlugin,createVirtualModulesPlugin,getViteDevServer,getAvalonConfig,isDevelopmentMode,VIRTUAL_MODULE_IDS,RESOLVED_VIRTUAL_IDS}from"./src/vite-plugin/nitro-integration.js";export{renderToHtml}from"./src/render/ssr.js";export{default as Island,renderIsland}from"./src/islands/island.js";export{addSvelteSSRCSS,getSvelteSSRCSS,getSvelteSSRCSSForHead,getSvelteSSRCSSStats,getSvelteComponentCSS,clearSvelteComponentCSS,generateComponentScopeId}from"./src/islands/css-utils.js";export{detectFramework,detectFrameworkFromSrc,resolveIslandPath}from"./src/islands/framework-detection.js";export{analyzeComponentFile,renderComponentSSROnly}from"./src/islands/component-analysis.js";export{registerHydrationDirective,unregisterHydrationDirective,isCustomDirective,getDirective,getRegisteredDirectives}from"./src/islands/hydration-directives.js";export{registerBuiltinDirectives}from"./src/islands/builtin-directives.js";export{clearCache,clearIslandCache,invalidateCacheForPath,invalidateCacheForFile,getCacheStats,logCacheStats,configureCache,getCacheConfig}from"./src/islands/render-cache.js";export{discoverIslandDirectories,discoverIslandsInDirectory,discoverAllIslands,isIslandsDirectory,getDefaultIslandsPath,hasDefaultIslandsDirectory,getQualifiedIslandName,parseQualifiedIslandName,IslandRegistry,createIslandRegistry,IslandResolver,createIslandResolver,IslandValidator,createIslandValidator,validateAllIslands,formatValidationError,formatValidationWarning,formatCircularDependency,formatValidationResult,IslandWatcher,createIslandWatcher,ISLAND_FILE_EXTENSIONS,DEFAULT_DISCOVERY_CONFIG,isSupportedIslandExtension}from"./src/islands/discovery/index.js";export{loadIntegration,detectAndLoadIntegration,preloadIntegrations,detectFrameworksFromPageContent,DEFAULT_PRELOAD_FRAMEWORKS}from"./src/islands/integration-loader.js";export{registry as integrationRegistry}from"./src/core/integrations/registry.js";export{generateIslandManifest,loadIslandManifest,getIslandBundlePath}from"./src/build/island-manifest.js";export{mdxIslandTransform}from"./src/build/mdx-island-transform.js";export{pageIslandTransform}from"./src/build/page-island-transform.js";export{asIsland}from"./src/types/as-island.js";export{generateIslandTypes,watchAndGenerateTypes}from"./src/build/island-types-generator.js";export async function build(e){throw Error("avalon build() is not available in the published package. Use `vite build` or the Avalon CLI instead.")}export{discoverScopedMiddleware,executeScopedMiddleware,clearMiddlewareCache,invalidateMiddleware,getMatchingMiddleware,clearDiscoveryCache,hasContextValue,getContextValue,setContextValue,getMiddlewareCacheSize}from"./src/middleware/index.js";export*from"./src/layout-system.js";export{IslandErrorBoundary,withIslandErrorBoundary}from"./src/components/IslandErrorBoundary.js";export{LayoutErrorBoundary}from"./src/components/LayoutErrorBoundary.js";export{usePersistentState}from"./src/persistence/use-persistent-state.js";
@@ -1,21 +1,13 @@
1
1
  /**
2
- * Client-safe component exports for use in island components.
2
+ * Client-safe component exports.
3
3
  *
4
- * Import from '@useavalon/avalon/client' instead of '@useavalon/avalon' when
5
- * you need framework components inside islands. The main entry point
6
- * re-exports server-only code (nitro, h3, vite plugins) that can't
7
- * be bundled for the browser.
4
+ * Image optimization, error boundaries, and persistent state for islands.
8
5
  */
9
- export { PersistentIsland } from '../components/PersistentIsland.tsx';
10
- export { usePersistentIslandContext, PersistentIslandProvider, createPersistentIslandContext, } from '../core/islands/persistent-island-context.tsx';
11
- export { usePersistentState } from '../core/islands/use-persistent-state.ts';
12
- export { IslandPersistence, defaultIslandPersistence } from '../core/islands/island-persistence.ts';
13
- export { IslandStateSerializer } from '../core/islands/island-state-serializer.ts';
14
- export { IslandErrorBoundary, withIslandErrorBoundary } from '../components/IslandErrorBoundary.tsx';
15
- export { LayoutErrorBoundary } from '../components/LayoutErrorBoundary.tsx';
16
- export { LayoutDataErrorBoundary } from '../components/LayoutDataErrorBoundary.tsx';
17
- export { StreamingErrorBoundary, withStreamingErrorBoundary } from '../components/StreamingErrorBoundary.tsx';
18
- export { StreamingLayout, StreamingSuspense, withStreaming, useStreamingState } from '../components/StreamingLayout.tsx';
19
6
  export { Image } from '../components/Image.tsx';
20
7
  export type { ImageProps } from '../components/Image.tsx';
21
- export type { IslandState } from '../schemas/layout.ts';
8
+ export { IslandErrorBoundary, withIslandErrorBoundary } from '../components/IslandErrorBoundary.tsx';
9
+ export type { IslandErrorBoundaryProps } from '../components/IslandErrorBoundary.tsx';
10
+ export { LayoutErrorBoundary } from '../components/LayoutErrorBoundary.tsx';
11
+ export type { LayoutErrorBoundaryProps } from '../components/LayoutErrorBoundary.tsx';
12
+ export { usePersistentState } from '../persistence/use-persistent-state.ts';
13
+ export { registerClientDirective } from './custom-directives.js';
@@ -1 +1 @@
1
- export{PersistentIsland}from"../components/PersistentIsland.tsx";export{usePersistentIslandContext,PersistentIslandProvider,createPersistentIslandContext}from"../core/islands/persistent-island-context.tsx";export{usePersistentState}from"../core/islands/use-persistent-state.js";export{IslandPersistence,defaultIslandPersistence}from"../core/islands/island-persistence.js";export{IslandStateSerializer}from"../core/islands/island-state-serializer.js";export{IslandErrorBoundary,withIslandErrorBoundary}from"../components/IslandErrorBoundary.tsx";export{LayoutErrorBoundary}from"../components/LayoutErrorBoundary.tsx";export{LayoutDataErrorBoundary}from"../components/LayoutDataErrorBoundary.tsx";export{StreamingErrorBoundary,withStreamingErrorBoundary}from"../components/StreamingErrorBoundary.tsx";export{StreamingLayout,StreamingSuspense,withStreaming,useStreamingState}from"../components/StreamingLayout.tsx";export{Image}from"../components/Image.tsx";
1
+ export{PersistentIsland}from"../components/PersistentIsland.tsx";export{usePersistentIslandContext,PersistentIslandProvider,createPersistentIslandContext}from"../core/islands/persistent-island-context.tsx";export{usePersistentState}from"../core/islands/use-persistent-state.js";export{IslandPersistence,defaultIslandPersistence}from"../core/islands/island-persistence.js";export{IslandStateSerializer}from"../core/islands/island-state-serializer.js";export{IslandErrorBoundary,withIslandErrorBoundary}from"../components/IslandErrorBoundary.tsx";export{LayoutErrorBoundary}from"../components/LayoutErrorBoundary.tsx";export{LayoutDataErrorBoundary}from"../components/LayoutDataErrorBoundary.tsx";export{StreamingErrorBoundary,withStreamingErrorBoundary}from"../components/StreamingErrorBoundary.tsx";export{StreamingLayout,StreamingSuspense,withStreaming,useStreamingState}from"../components/StreamingLayout.tsx";export{Image}from"../components/Image.tsx";export{registerClientDirective}from"./custom-directives.js";
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Type declarations for the client-side custom directives module.
3
+ */
4
+
5
+ /**
6
+ * Register a client-side directive function at runtime.
7
+ */
8
+ export declare function registerClientDirective(
9
+ name: string,
10
+ fn: (el: HTMLElement, hydrate: () => void, arg?: string) => void,
11
+ ): void;
12
+
13
+ /**
14
+ * Check if a directive is registered on the client.
15
+ */
16
+ export declare function hasClientDirective(name: string): boolean;
17
+
18
+ /**
19
+ * Execute a custom directive for an island element.
20
+ */
21
+ export declare function executeCustomDirective(
22
+ island: HTMLElement,
23
+ directiveName: string,
24
+ hydrateFn: () => void,
25
+ ): boolean;
@@ -0,0 +1 @@
1
+ const e=new Map;export function registerClientDirective(t,n){e.set(t,n)}export function hasClientDirective(t){return e.has(t)}export function executeCustomDirective(t,n,r){let i=t.dataset.conditionArg||void 0;if(e.has(n)){let a=e.get(n);try{a(t,r,i)}catch(e){console.error(`[avalon] Custom directive "${n}" threw:`,e),r()}return!0}let a=t.dataset.directiveScript;if(a){try{let o=Function(`return (`+a+`)`)();e.set(n,o),o(t,r,i)}catch(e){console.error(`[avalon] Failed to execute inline directive "${n}":`,e),r()}return!0}return!1}typeof globalThis<`u`&&(globalThis.__avalon_registerDirective=registerClientDirective);
@@ -1,4 +1,4 @@
1
- document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,e):e();function e(){let e=document.querySelectorAll(`[data-framework]`);e.length!==0&&e.forEach(e=>{try{let o=e.dataset.framework,s=e.dataset.condition||`on:client`;if(e.dataset.renderStrategy===`ssr-only`||!t(e,s))return;s===`on:client`?c(e,o):s===`on:visible`?n(e,o):s===`on:interaction`?r(e,o):s===`on:idle`?i(e,o):s.startsWith(`media:`)?a(e,o,s.slice(6)):c(e,o)}catch(t){console.error(`Error processing island:`,t),l(e,e.dataset.framework||`unknown`,e.dataset.src||`unknown`,t)}})}function t(e,t){if(!t||t===`on:client`)return!0;if(t.startsWith(`media:`)){let e=t.slice(6);try{return globalThis.matchMedia(e).matches}catch(t){return console.error(`Invalid media query:`,e,t),!0}}return t===`on:visible`||t===`on:interaction`||t===`on:idle`||console.warn(`Unknown hydration condition:`,t),!0}function n(e,t){try{let n=new IntersectionObserver(r=>{r[0].isIntersecting&&(c(e,t),n.disconnect())},{rootMargin:`50px`,threshold:0});n.observe(e)}catch(n){console.error(`Failed to setup intersection observer:`,n),c(e,t)}}function r(e,t){let n=[`click`,`touchstart`,`mouseenter`,`focusin`],r=!1,i=()=>{r||(r=!0,n.forEach(t=>{e.removeEventListener(t,i)}),c(e,t))};try{n.forEach(t=>{e.addEventListener(t,i,{once:!0,passive:!0})})}catch(n){console.error(`Failed to setup interaction observer:`,n),c(e,t)}}function i(e,t){try{`requestIdleCallback`in globalThis?globalThis.requestIdleCallback(()=>{c(e,t)},{timeout:5e3}):document.readyState===`complete`?setTimeout(()=>{c(e,t)},200):globalThis.addEventListener(`load`,()=>{setTimeout(()=>{c(e,t)},200)},{once:!0})}catch(n){console.error(`Failed to setup idle callback:`,n),c(e,t)}}function a(e,t,n){try{let r=globalThis.matchMedia(n);if(r.matches){c(e,t);return}let i=n=>{n.matches&&(c(e,t),r.removeEventListener(`change`,i))};r.addEventListener(`change`,i)}catch(r){console.error(`Failed to setup media query:`,n,r),c(e,t)}}async function o(e){if(![`preact`,`react`,`vue`,`svelte`,`solid`,`lit`,`qwik`].includes(e))throw Error(`Unknown framework: ${e}`);if(import.meta.env?.DEV)return import(`/@useavalon/${e}/client`);switch(e){case`preact`:case`react`:return import(`@useavalon/preact/client`);case`vue`:return import(`@useavalon/vue/client`);case`svelte`:return import(`@useavalon/svelte/client`);case`solid`:return import(`@useavalon/solid/client`);case`lit`:return import(`@useavalon/lit/client`);case`qwik`:return import(`@useavalon/qwik/client`);default:throw Error(`Unknown framework: ${e}`)}}function s(e,t){let n=e.default;if(!n){let t=Object.keys(e).filter(e=>e!==`default`);for(let r of t){let t=e[r];if(typeof t==`function`&&t.prototype){n=t;break}}n||=e}if(!n)throw Error(`Component ${t} has no default export`);return n}async function c(e,t){if(e.dataset.hydrated)return;let n=e.dataset.src,r=e.dataset.props;if(!n){console.warn(`Island missing data-src attribute`);return}try{let i=r?JSON.parse(r):{};t===`lit`&&(import.meta.env?.DEV?await import(`/@useavalon/lit/client`):await import(`@useavalon/lit/client`));let a=s(await import(n),n);try{let n=await o(t);if(!n.hydrate||typeof n.hydrate!=`function`)throw Error(`Integration ${t} does not export a hydrate function`);await n.hydrate(e,a,i),e.dataset.hydrated=`true`}catch(r){import.meta.env?.DEV&&console.error(`Integration hydration failed for ${t}: ${n}`,r),e.dataset.hydrationStatus=`failed`,e.dataset.hydrationError=r.message,e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:r.message,timestamp:Date.now(),hydrationType:`integration-level`},bubbles:!0}))}}catch(r){console.error(`❌ Critical error hydrating ${t} island ${n}:`,r),l(e,t,n,r)}}function l(e,t,n,r){console.error(`Hydration error for ${t} island:`,{src:n,error:r.message,stack:r.stack}),e.dataset.hydrationStatus=`failed`,e.dataset.renderStrategy=`ssr-only`,e.classList.add(`hydration-failed`),e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:r.message,timestamp:Date.now()},bubbles:!0})),d()&&u(e,t,n,r)}function u(e,t,n,r){let i=document.createElement(`div`);i.className=`hydration-error-indicator`,i.style.cssText=`
1
+ import{executeCustomDirective as e,hasClientDirective as t}from"./custom-directives.js";document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,n):n();function n(){let n=document.querySelectorAll(`[data-framework]`);n.length!==0&&n.forEach(n=>{try{let c=n.dataset.framework,l=n.dataset.condition||`on:client`;if(n.dataset.renderStrategy===`ssr-only`||!r(n,l))return;l===`on:client`?u(n,c):l===`on:visible`?i(n,c):l===`on:interaction`?a(n,c):l===`on:idle`?o(n,c):l.startsWith(`media:`)?s(n,c,l.slice(6)):n.dataset.customDirective||t(l)?e(n,l,()=>{u(n,c)})||(console.warn(`[avalon] Unknown hydration condition: "${l}". Hydrating immediately.`),u(n,c)):u(n,c)}catch(e){console.error(`Error processing island:`,e),d(n,n.dataset.framework||`unknown`,n.dataset.src||`unknown`,e)}})}function r(e,t){if(!t||t===`on:client`)return!0;if(t.startsWith(`media:`)){let e=t.slice(6);try{return globalThis.matchMedia(e).matches}catch(t){return console.error(`Invalid media query:`,e,t),!0}}return t===`on:visible`||t===`on:interaction`||t===`on:idle`||console.warn(`Unknown hydration condition:`,t),!0}function i(e,t){try{let n=new IntersectionObserver(r=>{r[0].isIntersecting&&(u(e,t),n.disconnect())},{rootMargin:`50px`,threshold:0});n.observe(e)}catch(n){console.error(`Failed to setup intersection observer:`,n),u(e,t)}}function a(e,t){let n=[`click`,`touchstart`,`mouseenter`,`focusin`],r=!1,i=()=>{r||(r=!0,n.forEach(t=>{e.removeEventListener(t,i)}),u(e,t))};try{n.forEach(t=>{e.addEventListener(t,i,{once:!0,passive:!0})})}catch(n){console.error(`Failed to setup interaction observer:`,n),u(e,t)}}function o(e,t){try{`requestIdleCallback`in globalThis?globalThis.requestIdleCallback(()=>{u(e,t)},{timeout:5e3}):document.readyState===`complete`?setTimeout(()=>{u(e,t)},200):globalThis.addEventListener(`load`,()=>{setTimeout(()=>{u(e,t)},200)},{once:!0})}catch(n){console.error(`Failed to setup idle callback:`,n),u(e,t)}}function s(e,t,n){try{let r=globalThis.matchMedia(n);if(r.matches){u(e,t);return}let i=n=>{n.matches&&(u(e,t),r.removeEventListener(`change`,i))};r.addEventListener(`change`,i)}catch(r){console.error(`Failed to setup media query:`,n,r),u(e,t)}}async function c(e){if(![`preact`,`react`,`vue`,`svelte`,`solid`,`lit`,`qwik`].includes(e))throw Error(`Unknown framework: ${e}`);if(import.meta.env?.DEV)return import(`/@useavalon/${e}/client`);switch(e){case`preact`:case`react`:return import(`@useavalon/preact/client`);case`vue`:return import(`@useavalon/vue/client`);case`svelte`:return import(`@useavalon/svelte/client`);case`solid`:return import(`@useavalon/solid/client`);case`lit`:return import(`@useavalon/lit/client`);case`qwik`:return import(`@useavalon/qwik/client`);default:throw Error(`Unknown framework: ${e}`)}}function l(e,t){let n=e.default;if(!n){let t=Object.keys(e).filter(e=>e!==`default`);for(let r of t){let t=e[r];if(typeof t==`function`&&t.prototype){n=t;break}}n||=e}if(!n)throw Error(`Component ${t} has no default export`);return n}async function u(e,t){if(e.dataset.hydrated)return;let n=e.dataset.src,r=e.dataset.props;if(!n){console.warn(`Island missing data-src attribute`);return}try{let i=r?JSON.parse(r):{};t===`lit`&&(import.meta.env?.DEV?await import(`/@useavalon/lit/client`):await import(`@useavalon/lit/client`));let a=l(await import(n),n);try{let n=await c(t);if(!n.hydrate||typeof n.hydrate!=`function`)throw Error(`Integration ${t} does not export a hydrate function`);await n.hydrate(e,a,i),e.dataset.hydrated=`true`}catch(r){import.meta.env?.DEV&&console.error(`Integration hydration failed for ${t}: ${n}`,r),e.dataset.hydrationStatus=`failed`,e.dataset.hydrationError=r.message,e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:r.message,timestamp:Date.now(),hydrationType:`integration-level`},bubbles:!0}))}}catch(r){console.error(`❌ Critical error hydrating ${t} island ${n}:`,r),d(e,t,n,r)}}function d(e,t,n,r){console.error(`Hydration error for ${t} island:`,{src:n,error:r.message,stack:r.stack}),e.dataset.hydrationStatus=`failed`,e.dataset.renderStrategy=`ssr-only`,e.classList.add(`hydration-failed`),e.dispatchEvent(new CustomEvent(`hydration-error`,{detail:{framework:t,src:n,error:r.message,timestamp:Date.now()},bubbles:!0})),p()&&f(e,t,n,r)}function f(e,t,n,r){let i=document.createElement(`div`);i.className=`hydration-error-indicator`,i.style.cssText=`
2
2
  position: absolute;
3
3
  top: 0;
4
4
  right: 0;
@@ -11,7 +11,7 @@ document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,e):
11
11
  z-index: 9999;
12
12
  cursor: pointer;
13
13
  box-shadow: 0 2px 4px rgba(0,0,0,0.2);
14
- `,i.textContent=`❌ ${t}`,i.title=`Hydration failed: ${n}\n${r.message}\nClick for details`,i.addEventListener(`click`,()=>{alert(`Hydration Error\n\nFramework: ${t}\nComponent: ${n}\n\nError: ${r.message}\n\nStack:\n${r.stack}`)}),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.appendChild(i)}function d(){return import.meta.env?.DEV||import.meta.env?.MODE===`development`||globalThis.location?.hostname===`localhost`||globalThis.location?.hostname===`127.0.0.1`}function f(e){let t=e.dataset.framework,n=e.dataset.src;if(!n)return null;let r={framework:t,src:n,props:e.dataset.props,scrollPosition:{x:globalThis.scrollX,y:globalThis.scrollY},focusedElement:document.activeElement?.id||null};try{t===`vue`&&e.__vue__?r.vueData=structuredClone(e.__vue__.$data||{}):t===`svelte`&&e.__svelte__?r.svelteState=e.__svelte__:t===`lit`&&e.tagName?.includes(`-`)&&(e.querySelector(`[data-lit-element]`)||e)._$litElement$&&(r.litProperties={})}catch(e){console.warn(`Failed to preserve island state:`,e)}return r}function p(e,t){if(t)try{if(t.scrollPosition&&globalThis.scrollTo(t.scrollPosition.x,t.scrollPosition.y),t.focusedElement){let e=document.getElementById(t.focusedElement);e&&e.focus()}e.dataset.framework===`vue`&&t.vueData&&e.__vue__&&Object.assign(e.__vue__.$data,t.vueData)}catch(e){console.warn(`Failed to restore island state:`,e)}}async function m(e,t,n,r){let i=e.dataset.props,a=i?JSON.parse(i):{};t===`lit`&&(import.meta.env?.DEV?await import(`/@useavalon/lit/client`):await import(`@useavalon/lit/client`));let c=s(await import(n),r),l=await o(t);if(!l.hydrate||typeof l.hydrate!=`function`)throw Error(`Integration ${t} does not export a hydrate function`);l.hydrate(e,c,a),e.dataset.hydrated=`true`}function h(e,t,n,r){let i=e.querySelector(`.hmr-error-indicator`);i&&i.remove();let a=document.createElement(`div`);a.className=`hmr-error-indicator`,a.style.cssText=`
14
+ `,i.textContent=`❌ ${t}`,i.title=`Hydration failed: ${n}\n${r.message}\nClick for details`,i.addEventListener(`click`,()=>{alert(`Hydration Error\n\nFramework: ${t}\nComponent: ${n}\n\nError: ${r.message}\n\nStack:\n${r.stack}`)}),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.appendChild(i)}function p(){return import.meta.env?.DEV||import.meta.env?.MODE===`development`||globalThis.location?.hostname===`localhost`||globalThis.location?.hostname===`127.0.0.1`}function m(e){let t=e.dataset.framework,n=e.dataset.src;if(!n)return null;let r={framework:t,src:n,props:e.dataset.props,scrollPosition:{x:globalThis.scrollX,y:globalThis.scrollY},focusedElement:document.activeElement?.id||null};try{t===`vue`&&e.__vue__?r.vueData=structuredClone(e.__vue__.$data||{}):t===`svelte`&&e.__svelte__?r.svelteState=e.__svelte__:t===`lit`&&e.tagName?.includes(`-`)&&(e.querySelector(`[data-lit-element]`)||e)._$litElement$&&(r.litProperties={})}catch(e){console.warn(`Failed to preserve island state:`,e)}return r}function h(e,t){if(t)try{if(t.scrollPosition&&globalThis.scrollTo(t.scrollPosition.x,t.scrollPosition.y),t.focusedElement){let e=document.getElementById(t.focusedElement);e&&e.focus()}e.dataset.framework===`vue`&&t.vueData&&e.__vue__&&Object.assign(e.__vue__.$data,t.vueData)}catch(e){console.warn(`Failed to restore island state:`,e)}}async function g(e,t,n,r){let i=e.dataset.props,a=i?JSON.parse(i):{};t===`lit`&&(import.meta.env?.DEV?await import(`/@useavalon/lit/client`):await import(`@useavalon/lit/client`));let o=l(await import(n),r),s=await c(t);if(!s.hydrate||typeof s.hydrate!=`function`)throw Error(`Integration ${t} does not export a hydrate function`);s.hydrate(e,o,a),e.dataset.hydrated=`true`}function _(e,t,n,r){let i=e.querySelector(`.hmr-error-indicator`);i&&i.remove();let a=document.createElement(`div`);a.className=`hmr-error-indicator`,a.style.cssText=`
15
15
  position: absolute;
16
16
  top: 0;
17
17
  left: 0;
@@ -36,4 +36,4 @@ document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,e):
36
36
  cursor: pointer;
37
37
  font-size: 14px;
38
38
  line-height: 1;
39
- `,c.onclick=()=>a.remove(),a.appendChild(o),a.appendChild(s),a.appendChild(c),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.insertBefore(a,e.firstChild)}import.meta.hot&&(import.meta.hot.accept(),import(`./hmr-coordinator.js`).then(async({initializeHMR:e,getHMRCoordinator:t})=>{e();let n=t(),r=new Set;document.querySelectorAll(`[data-framework]`).forEach(e=>{let t=e.dataset.framework;t&&r.add(t)});let i=e=>import(`/@useavalon/${e}/client/hmr`).then(t=>t[`${e}Adapter`]),a={react:()=>i(`react`),preact:()=>i(`preact`),vue:()=>i(`vue`),svelte:()=>i(`svelte`),solid:()=>i(`solid`),lit:()=>i(`lit`),qwik:()=>i(`qwik`)};for(let e of r){let t=a[e];if(t)try{let r=await t();n.registerAdapter(e,r)}catch(t){console.warn(`[HMR] Failed to load adapter for ${e}:`,t)}}}).catch(e=>{console.error(`[HMR] Failed to initialize:`,e)}),_());async function g(e,t,n,r){try{let{showHMRErrorOverlay:e}=await import(`./hmr-error-overlay.js`);e({framework:t,src:n,error:r,filePath:n})}catch{h(e,t,n,r)}}function _(){if(!import.meta.hot)return;let e=new Map;async function t(e){let t=e.replaceAll(`\\`,`/`),r=document.querySelectorAll(`[data-src*="${t}"], [data-src$="${t}"]`);if(r.length===0){let e=document.querySelectorAll(`[data-src]`);for(let r of e){let e=r.dataset.src;e&&(e.includes(t)||t.includes(e.replace(/^\//,``)))&&await n(r)}return}for(let e of r)await n(e)}async function n(t){let n=t.dataset.framework,r=t.dataset.src;if(!(!r||!n))try{let i=f(t);e.set(r,i),delete t.dataset.hydrated,delete t.dataset.hydrationStatus;let a=t.querySelector(`.hydration-error-indicator`);a&&a.remove();let o=Date.now();await m(t,n,r.includes(`?`)?`${r}&t=${o}`:`${r}?t=${o}`,r);let s=e.get(r);s&&(p(t,s),e.delete(r)),t.dispatchEvent(new CustomEvent(`hmr-update`,{detail:{framework:n,src:r,timestamp:Date.now(),success:!0},bubbles:!0}))}catch(e){console.error(`[HMR] Failed for ${n} island ${r}:`,e),t.dispatchEvent(new CustomEvent(`hmr-error`,{detail:{framework:n,src:r,error:e.message,timestamp:Date.now()},bubbles:!0})),d()&&g(t,n,r,e)}}import.meta.hot.on(`vite:beforeUpdate`,e=>{for(let n of e.updates||[]){let e=n.path||n.acceptedPath;e&&(e.includes(`/islands/`)||e.includes(`\\islands\\`))&&t(e)}}),import.meta.hot.on(`vite:beforeFullReload`,()=>{let e=document.querySelectorAll(`[data-hydrated="true"]`),t={};for(let n of e){let e=n.dataset.src;e&&(t[e]=f(n))}try{sessionStorage.setItem(`__avalon_hmr_states__`,JSON.stringify(t))}catch{}});try{let e=sessionStorage.getItem(`__avalon_hmr_states__`);if(e){let t=JSON.parse(e);sessionStorage.removeItem(`__avalon_hmr_states__`),setTimeout(()=>{for(let[e,n]of Object.entries(t)){let t=document.querySelector(`[data-src="${e}"]`);t&&n&&p(t,n)}},100)}}catch{}}
39
+ `,c.onclick=()=>a.remove(),a.appendChild(o),a.appendChild(s),a.appendChild(c),globalThis.getComputedStyle(e).position===`static`&&(e.style.position=`relative`),e.insertBefore(a,e.firstChild)}import.meta.hot&&(import.meta.hot.accept(),import(`./hmr-coordinator.js`).then(async({initializeHMR:e,getHMRCoordinator:t})=>{e();let n=t(),r=new Set;document.querySelectorAll(`[data-framework]`).forEach(e=>{let t=e.dataset.framework;t&&r.add(t)});let i=e=>import(`/@useavalon/${e}/client/hmr`).then(t=>t[`${e}Adapter`]),a={react:()=>i(`react`),preact:()=>i(`preact`),vue:()=>i(`vue`),svelte:()=>i(`svelte`),solid:()=>i(`solid`),lit:()=>i(`lit`),qwik:()=>i(`qwik`)};for(let e of r){let t=a[e];if(t)try{let r=await t();n.registerAdapter(e,r)}catch(t){console.warn(`[HMR] Failed to load adapter for ${e}:`,t)}}}).catch(e=>{console.error(`[HMR] Failed to initialize:`,e)}),y());async function v(e,t,n,r){try{let{showHMRErrorOverlay:e}=await import(`./hmr-error-overlay.js`);e({framework:t,src:n,error:r,filePath:n})}catch{_(e,t,n,r)}}function y(){if(!import.meta.hot)return;let e=new Map;async function t(e){let t=e.replaceAll(`\\`,`/`),r=document.querySelectorAll(`[data-src*="${t}"], [data-src$="${t}"]`);if(r.length===0){let e=document.querySelectorAll(`[data-src]`);for(let r of e){let e=r.dataset.src;e&&(e.includes(t)||t.includes(e.replace(/^\//,``)))&&await n(r)}return}for(let e of r)await n(e)}async function n(t){let n=t.dataset.framework,r=t.dataset.src;if(!(!r||!n))try{let i=m(t);e.set(r,i),delete t.dataset.hydrated,delete t.dataset.hydrationStatus;let a=t.querySelector(`.hydration-error-indicator`);a&&a.remove();let o=Date.now();await g(t,n,r.includes(`?`)?`${r}&t=${o}`:`${r}?t=${o}`,r);let s=e.get(r);s&&(h(t,s),e.delete(r)),t.dispatchEvent(new CustomEvent(`hmr-update`,{detail:{framework:n,src:r,timestamp:Date.now(),success:!0},bubbles:!0}))}catch(e){console.error(`[HMR] Failed for ${n} island ${r}:`,e),t.dispatchEvent(new CustomEvent(`hmr-error`,{detail:{framework:n,src:r,error:e.message,timestamp:Date.now()},bubbles:!0})),p()&&v(t,n,r,e)}}import.meta.hot.on(`vite:beforeUpdate`,e=>{for(let n of e.updates||[]){let e=n.path||n.acceptedPath;e&&(e.includes(`/islands/`)||e.includes(`\\islands\\`))&&t(e)}}),import.meta.hot.on(`vite:beforeFullReload`,()=>{let e=document.querySelectorAll(`[data-hydrated="true"]`),t={};for(let n of e){let e=n.dataset.src;e&&(t[e]=m(n))}try{sessionStorage.setItem(`__avalon_hmr_states__`,JSON.stringify(t))}catch{}});try{let e=sessionStorage.getItem(`__avalon_hmr_states__`);if(e){let t=JSON.parse(e);sessionStorage.removeItem(`__avalon_hmr_states__`),setTimeout(()=>{for(let[e,n]of Object.entries(t)){let t=document.querySelector(`[data-src="${e}"]`);t&&n&&h(t,n)}},100)}}catch{}}
@@ -1,20 +1,51 @@
1
- import { Component, ComponentChildren, ComponentType } from 'preact';
2
- import type { LayoutErrorInfo } from '../types/layout.ts';
1
+ /** @jsxImportSource preact */
2
+ import { Component, type ComponentChildren, type ComponentType } from 'preact';
3
+ import type { LayoutErrorInfo } from '../schemas/layout.ts';
4
+ /**
5
+ * Props for {@link IslandErrorBoundary}.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * <IslandErrorBoundary
10
+ * islandId="counter"
11
+ * isolateError
12
+ * onError={(err, info) => console.error(info.errorBoundary, err)}
13
+ * fallback={(err, id) => <p>Island "{id}" failed: {err.message}</p>}
14
+ * >
15
+ * <Counter />
16
+ * </IslandErrorBoundary>
17
+ * ```
18
+ */
3
19
  export interface IslandErrorBoundaryProps {
20
+ /** The content to render inside the error boundary. */
4
21
  children: ComponentChildren;
22
+ /** Unique identifier for the island — used in error reports and DOM attributes. */
5
23
  islandId: string;
24
+ /** Called when the island throws. Receives the error and structured error info. */
6
25
  onError?: (error: Error, errorInfo: LayoutErrorInfo) => void;
26
+ /** Custom fallback UI. When omitted a default error card is shown. */
7
27
  fallback?: (error: Error, islandId: string) => ComponentChildren;
28
+ /** When `true` (default for HOC), the error is caught and isolated. When `false`, it re-throws to parent boundaries. */
8
29
  isolateError?: boolean;
9
30
  }
10
- export interface IslandErrorBoundaryState {
31
+ interface IslandErrorBoundaryState {
11
32
  hasError: boolean;
12
33
  error: Error | null;
13
34
  errorInfo: LayoutErrorInfo | null;
14
35
  }
15
36
  /**
16
- * Specialized error boundary for island components
17
- * Provides error isolation to prevent island errors from affecting the main layout
37
+ * Error boundary that wraps individual islands to prevent one broken island
38
+ * from taking down the entire page.
39
+ *
40
+ * Renders a default error card with "Reload" / "Remove" actions, or your
41
+ * custom `fallback` if provided. In development mode, a stack trace is shown.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * <IslandErrorBoundary islandId="cart" isolateError>
46
+ * <CartIsland />
47
+ * </IslandErrorBoundary>
48
+ * ```
18
49
  */
19
50
  export declare class IslandErrorBoundary extends Component<IslandErrorBoundaryProps, IslandErrorBoundaryState> {
20
51
  constructor(props: IslandErrorBoundaryProps);
@@ -22,16 +53,24 @@ export declare class IslandErrorBoundary extends Component<IslandErrorBoundaryPr
22
53
  componentDidCatch(error: Error, errorInfo: {
23
54
  componentStack?: string;
24
55
  }): void;
25
- private handleRemoveIsland;
26
- private handleReloadIsland;
27
- private renderFallback;
56
+ private readonly handleRemoveIsland;
57
+ private readonly handleReloadIsland;
28
58
  render(): ComponentChildren;
29
59
  }
30
60
  /**
31
- * Higher-order component to wrap islands with error boundaries
61
+ * HOC that wraps a component with {@link IslandErrorBoundary}.
62
+ *
63
+ * @example
64
+ * ```tsx
65
+ * const SafeCounter = withIslandErrorBoundary(Counter, 'counter', {
66
+ * isolateError: true,
67
+ * fallback: (err) => <p>Oops: {err.message}</p>,
68
+ * });
69
+ * ```
32
70
  */
33
71
  export declare function withIslandErrorBoundary<P extends object>(WrappedComponent: ComponentType<P>, islandId: string, options?: {
34
72
  fallback?: (error: Error, islandId: string) => ComponentChildren;
35
73
  isolateError?: boolean;
36
74
  onError?: (error: Error, errorInfo: LayoutErrorInfo) => void;
37
75
  }): (props: P) => import("preact").JSX.Element;
76
+ export {};
@@ -1 +1 @@
1
- import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class IslandErrorBoundary extends e{constructor(e){super(e),this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){let n={layoutPath:`island:${this.props.islandId}`,errorType:`island`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:`IslandErrorBoundary`};if(this.setState({errorInfo:n}),this.props.onError&&this.props.onError(e,n),typeof process<`u`&&process.env?.NODE_ENV===`development`&&console.error(`Island Error [${this.props.islandId}]:`,e),!this.props.isolateError)throw e}handleRemoveIsland=()=>{let e=document.querySelector(`[data-island-id="${this.props.islandId}"]`);e&&e.remove()};handleReloadIsland=()=>{this.setState({hasError:!1,error:null,errorInfo:null})};renderFallback(){let{error:e}=this.state,{fallback:r,islandId:i}=this.props;if(r&&e)return r(e,i);let a=typeof process<`u`&&process.env?.NODE_ENV===`development`;return t(`div`,{class:`island-error-boundary`,"data-island-error":i,children:n(`div`,{class:`island-error-container`,children:[n(`div`,{class:`island-error-header`,children:[t(`span`,{class:`island-error-icon`,children:`⚠️`}),t(`span`,{class:`island-error-title`,children:`Island Error`})]}),n(`p`,{class:`island-error-message`,children:[`An error occurred in island "`,i,`". The rest of the page should work normally.`]}),n(`div`,{class:`island-error-actions`,children:[t(`button`,{onClick:this.handleReloadIsland,class:`island-reload-button`,children:`Reload Island`}),t(`button`,{onClick:this.handleRemoveIsland,class:`island-remove-button`,children:`Remove Island`})]}),a&&e&&n(`details`,{class:`island-error-details`,children:[t(`summary`,{children:`Error Details (Development)`}),n(`div`,{class:`island-error-info`,children:[n(`p`,{children:[t(`strong`,{children:`Island ID:`}),` `,i]}),n(`p`,{children:[t(`strong`,{children:`Error:`}),` `,e.message]})]}),t(`pre`,{class:`island-error-stack`,children:e.stack})]})]})})}render(){return this.state.hasError?this.renderFallback():this.props.children}}export function withIslandErrorBoundary(e,n,i){return function(a){return t(IslandErrorBoundary,{islandId:n,fallback:i?.fallback,isolateError:i?.isolateError??!0,onError:i?.onError,children:t(e,{...a})})}}
1
+ import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class IslandErrorBoundary extends e{constructor(e){super(e),this.state={hasError:!1,error:null,errorInfo:null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){let n={layoutPath:`island:${this.props.islandId}`,errorType:`island`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:`IslandErrorBoundary`};if(this.setState({errorInfo:n}),this.props.onError?.(e,n),!this.props.isolateError)throw e}handleRemoveIsland=()=>{document.querySelector(`[data-island-id="${this.props.islandId}"]`)?.remove()};handleReloadIsland=()=>{this.setState({hasError:!1,error:null,errorInfo:null})};render(){if(!this.state.hasError)return this.props.children;let{error:e}=this.state,{fallback:r,islandId:i}=this.props;if(r&&e)return r(e,i);let a=typeof process<`u`&&process.env?.NODE_ENV===`development`;return t(`div`,{className:`island-error-boundary`,"data-island-error":i,children:n(`div`,{className:`island-error-container`,children:[n(`div`,{className:`island-error-header`,children:[t(`span`,{className:`island-error-icon`,children:`⚠️`}),t(`span`,{className:`island-error-title`,children:`Island Error`})]}),n(`p`,{className:`island-error-message`,children:[`An error occurred in island "`,i,`".`]}),n(`div`,{className:`island-error-actions`,children:[t(`button`,{onClick:this.handleReloadIsland,className:`island-reload-button`,children:`Reload Island`}),t(`button`,{onClick:this.handleRemoveIsland,className:`island-remove-button`,children:`Remove Island`})]}),a&&e&&n(`details`,{className:`island-error-details`,children:[t(`summary`,{children:`Error Details (Development)`}),n(`p`,{children:[t(`strong`,{children:`Island ID:`}),` `,i]}),n(`p`,{children:[t(`strong`,{children:`Error:`}),` `,e.message]}),t(`pre`,{className:`island-error-stack`,children:e.stack})]})]})})}}export function withIslandErrorBoundary(e,n,i){return function(a){return t(IslandErrorBoundary,{islandId:n,fallback:i?.fallback,isolateError:i?.isolateError??!0,onError:i?.onError,children:t(e,{...a})})}}
@@ -1,25 +1,90 @@
1
- import { Component, ComponentChildren } from 'preact';
2
- import type { LayoutErrorInfo, ErrorRecoveryStrategy } from '../types/layout.ts';
1
+ /** @jsxImportSource preact */
2
+ import { Component, type ComponentChildren } from 'preact';
3
+ import type { LayoutErrorInfo, LayoutData } from '../schemas/layout.ts';
4
+ /**
5
+ * Props for {@link LayoutErrorBoundary}.
6
+ *
7
+ * @example Basic usage
8
+ * ```tsx
9
+ * <LayoutErrorBoundary onError={(err) => logToSentry(err)}>
10
+ * <DashboardLayout />
11
+ * </LayoutErrorBoundary>
12
+ * ```
13
+ *
14
+ * @example Custom fallback with retry
15
+ * ```tsx
16
+ * <LayoutErrorBoundary
17
+ * fallback={(err, retry) => (
18
+ * <div>
19
+ * <p>Layout crashed: {err.message}</p>
20
+ * <button onClick={retry}>Retry</button>
21
+ * </div>
22
+ * )}
23
+ * >
24
+ * <DashboardLayout />
25
+ * </LayoutErrorBoundary>
26
+ * ```
27
+ *
28
+ * @example Data loading with async retry and cached fallback
29
+ * ```tsx
30
+ * <LayoutErrorBoundary
31
+ * retryLoader={() => fetch('/api/blog').then(r => r.json())}
32
+ * fallbackData={{ posts: [] }}
33
+ * >
34
+ * <BlogLayout />
35
+ * </LayoutErrorBoundary>
36
+ * ```
37
+ */
3
38
  export interface LayoutErrorBoundaryProps {
39
+ /** The layout tree to protect. */
4
40
  children: ComponentChildren;
41
+ /** Custom fallback UI. Receives the error and a retry callback. */
5
42
  fallback?: (error: Error, retry: () => void) => ComponentChildren;
43
+ /** Called when the layout throws. */
6
44
  onError?: (error: Error, errorInfo: LayoutErrorInfo) => void;
7
- recoveryStrategy?: ErrorRecoveryStrategy;
8
- layoutPath?: string;
45
+ /** Categorises the error for structured logging. Defaults to `'component'`. */
9
46
  errorType?: 'component' | 'loader' | 'rendering' | 'island';
47
+ /** Async function to re-attempt data loading. Enables the async retry button (up to 3 attempts). */
48
+ retryLoader?: () => Promise<LayoutData>;
49
+ /** Static data to offer as a "Use Cached Data" option when the loader fails. */
50
+ fallbackData?: LayoutData;
10
51
  }
11
- export interface LayoutErrorBoundaryState {
52
+ interface LayoutErrorBoundaryState {
12
53
  hasError: boolean;
13
54
  error: Error | null;
14
55
  errorInfo: LayoutErrorInfo | null;
15
56
  retryCount: number;
57
+ isRetrying: boolean;
58
+ fallbackData: LayoutData | null;
16
59
  }
60
+ /**
61
+ * Error boundary for Avalon layouts.
62
+ *
63
+ * Catches render and data-loading errors in the wrapped layout tree.
64
+ * Shows a default error card with up to 3 retries, or your custom
65
+ * `fallback`. When `retryLoader` is provided, retry is async. When
66
+ * `fallbackData` is provided, a "Use Cached Data" button appears.
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * <LayoutErrorBoundary
71
+ * retryLoader={() => fetchDashboardData()}
72
+ * fallbackData={{ widgets: [] }}
73
+ * onError={(err) => logToSentry(err)}
74
+ * >
75
+ * <DashboardLayout />
76
+ * </LayoutErrorBoundary>
77
+ * ```
78
+ */
17
79
  export declare class LayoutErrorBoundary extends Component<LayoutErrorBoundaryProps, LayoutErrorBoundaryState> {
18
- private maxRetries;
80
+ private readonly maxRetries;
19
81
  constructor(props: LayoutErrorBoundaryProps);
20
82
  static getDerivedStateFromError(error: Error): Partial<LayoutErrorBoundaryState>;
21
- componentDidCatch(error: Error, errorInfo: any): void;
22
- private handleRetry;
23
- private renderFallback;
83
+ componentDidCatch(error: Error, errorInfo: {
84
+ componentStack?: string;
85
+ }): void;
86
+ private readonly handleRetry;
87
+ private readonly handleUseFallback;
24
88
  render(): ComponentChildren;
25
89
  }
90
+ export {};
@@ -1 +1 @@
1
- import{Component as e}from"preact";import{jsxs as t,jsx as n}from"preact/jsx-runtime";export class LayoutErrorBoundary extends e{maxRetries=3;constructor(e){super(e),this.state={hasError:!1,error:null,errorInfo:null,retryCount:0}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){let n={layoutPath:this.props.layoutPath||`unknown`,errorType:this.props.errorType||`component`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:this.constructor.name};this.setState({errorInfo:n}),this.props.onError&&this.props.onError(e,n),typeof process<`u`&&process.env?.NODE_ENV===`development`&&(console.error(`Layout Error Boundary caught an error:`,e),console.error(`Error Info:`,n),console.error(`Component Stack:`,t.componentStack))}handleRetry=()=>{this.state.retryCount<this.maxRetries&&this.setState({hasError:!1,error:null,errorInfo:null,retryCount:this.state.retryCount+1})};renderFallback(){let{error:e}=this.state,{fallback:r}=this.props;return r&&e?r(e,this.handleRetry):n(`div`,{class:`layout-error-boundary`,children:t(`div`,{class:`error-container`,children:[n(`h2`,{children:`Something went wrong`}),n(`p`,{children:`An error occurred while rendering this layout.`}),this.state.retryCount<this.maxRetries&&t(`button`,{onClick:this.handleRetry,class:`retry-button`,children:[`Try Again (`,this.maxRetries-this.state.retryCount,` attempts left)`]}),typeof process<`u`&&process.env?.NODE_ENV===`development`&&t(`details`,{class:`error-details`,children:[n(`summary`,{children:`Error Details (Development)`}),n(`pre`,{children:e?.stack}),this.state.errorInfo&&t(`div`,{children:[t(`p`,{children:[n(`strong`,{children:`Layout Path:`}),` `,this.state.errorInfo.layoutPath]}),t(`p`,{children:[n(`strong`,{children:`Error Type:`}),` `,this.state.errorInfo.errorType]}),t(`p`,{children:[n(`strong`,{children:`Timestamp:`}),` `,new Date(this.state.errorInfo.timestamp).toISOString()]})]})]})]})})}render(){return this.state.hasError?this.renderFallback():this.props.children}}
1
+ import{Component as e}from"preact";import{jsx as t,jsxs as n}from"preact/jsx-runtime";export class LayoutErrorBoundary extends e{maxRetries=3;constructor(e){super(e),this.state={hasError:!1,error:null,errorInfo:null,retryCount:0,isRetrying:!1,fallbackData:e.fallbackData??null}}static getDerivedStateFromError(e){return{hasError:!0,error:e}}componentDidCatch(e,t){let n={errorType:this.props.errorType??`component`,timestamp:Date.now(),componentStack:t.componentStack,errorBoundary:`LayoutErrorBoundary`,layoutPath:void 0};this.setState({errorInfo:n}),this.props.onError?.(e,n)}handleRetry=async()=>{if(!(this.state.retryCount>=this.maxRetries))if(this.props.retryLoader){this.setState({isRetrying:!0});try{let e=await this.props.retryLoader();this.setState(t=>({hasError:!1,error:null,errorInfo:null,retryCount:t.retryCount+1,isRetrying:!1,fallbackData:e}))}catch(e){this.setState(t=>({retryCount:t.retryCount+1,isRetrying:!1,error:e instanceof Error?e:Error(String(e))}))}}else this.setState(e=>({hasError:!1,error:null,errorInfo:null,retryCount:e.retryCount+1}))};handleUseFallback=()=>{this.state.fallbackData&&this.setState({hasError:!1,error:null,errorInfo:null})};render(){if(!this.state.hasError)return this.props.children;let{error:e,retryCount:r,isRetrying:i,fallbackData:a}=this.state;if(this.props.fallback&&e)return this.props.fallback(e,this.handleRetry);let o=r<this.maxRetries,s=typeof process<`u`&&process.env?.NODE_ENV===`development`;return t(`div`,{className:`layout-error-boundary`,children:n(`div`,{className:`error-container`,children:[t(`h2`,{children:`Something went wrong`}),t(`p`,{children:`An error occurred while rendering this layout.`}),n(`div`,{className:`error-actions`,children:[o&&t(`button`,{onClick:this.handleRetry,disabled:i,className:`retry-button`,children:i?`Retrying...`:`Try Again (${this.maxRetries-r} left)`}),a!==null&&t(`button`,{onClick:this.handleUseFallback,className:`fallback-button`,children:`Use Cached Data`})]}),s&&e&&n(`details`,{className:`error-details`,children:[t(`summary`,{children:`Error Details (Development)`}),n(`p`,{children:[t(`strong`,{children:`Error:`}),` `,e.message]}),this.state.errorInfo&&n(`p`,{children:[t(`strong`,{children:`Error Type:`}),` `,this.state.errorInfo.errorType]}),t(`pre`,{className:`error-stack`,children:e.stack})]})]})})}}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Built-in custom hydration directives.
3
+ *
4
+ * These extend the core set (on:client, on:visible, on:interaction, on:idle, media:*)
5
+ * with additional strategies that users can opt into.
6
+ *
7
+ * Import and call `registerBuiltinDirectives()` in your server entry
8
+ * to make them available.
9
+ *
10
+ * @module islands/builtin-directives
11
+ */
12
+ /**
13
+ * Register all built-in custom directives.
14
+ */
15
+ export declare function registerBuiltinDirectives(): void;
@@ -0,0 +1 @@
1
+ import{registerHydrationDirective as e}from"./hydration-directives.js";export function registerBuiltinDirectives(){e(`on:delay`,{name:`on:delay`,script:(e,t,n)=>{setTimeout(t,Number.parseInt(n||`1000`,10))}}),e(`on:event`,{name:`on:event`,script:(e,t,n)=>{if(!n){t();return}let r=()=>{document.removeEventListener(n,r),t()};document.addEventListener(n,r,{once:!0})}}),e(`on:scroll`,{name:`on:scroll`,script:(e,t,n)=>{let r=Number.parseInt(n||`100`,10),i=()=>{globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())};globalThis.addEventListener(`scroll`,i,{passive:!0}),globalThis.scrollY>=r&&(globalThis.removeEventListener(`scroll`,i),t())}}),e(`on:match`,{name:`on:match`,script:(e,t,n)=>{if(!n){t();return}let r=globalThis.matchMedia(n);if(r.matches){t();return}let i=e=>{e.matches&&(r.removeEventListener(`change`,i),t())};r.addEventListener(`change`,i)}})}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Custom Hydration Directives
3
+ *
4
+ * Allows users to define custom hydration strategies for islands.
5
+ * A directive controls _when_ an island hydrates on the client.
6
+ *
7
+ * Built-in directives (on:client, on:visible, on:interaction, on:idle, media:*)
8
+ * are handled natively in main.js. This module enables user-defined directives
9
+ * that extend the system with arbitrary trigger logic.
10
+ *
11
+ * @example
12
+ * @example
13
+ * ```ts
14
+ * // Register a directive that hydrates after a delay
15
+ * registerHydrationDirective('on:delay', {
16
+ * name: 'on:delay',
17
+ * // Runs on the client — receives the island element and a hydrate callback
18
+ * script: (el, hydrate, arg) => {
19
+ * const ms = parseInt(arg || '1000', 10);
20
+ * setTimeout(hydrate, ms);
21
+ * },
22
+ * });
23
+ *
24
+ * // Use in a page via the island prop
25
+ * <Counter island={{ condition: 'on:delay', conditionArg: '2000' }} />
26
+ * ```
27
+ * @module islands/hydration-directives
28
+ */
29
+ /**
30
+ * A client-side hydration directive function.
31
+ *
32
+ * Called once per island element when the page initializes.
33
+ * The function must call `hydrate()` exactly once when the island
34
+ * should become interactive.
35
+ *
36
+ * @param el - The `<avalon-island>` DOM element
37
+ * @param hydrate - Callback that triggers hydration. Call it once.
38
+ * @param arg - Optional argument string from `conditionArg` prop
39
+ */
40
+ export type HydrationDirectiveFn = (el: HTMLElement, hydrate: () => void, arg?: string) => void | (() => void);
41
+ /**
42
+ * Definition of a custom hydration directive.
43
+ */
44
+ export interface HydrationDirectiveDefinition {
45
+ /** Directive name — must match the `condition` prop value (e.g. "on:delay") */
46
+ name: string;
47
+ /**
48
+ * Client-side script that controls when hydration fires.
49
+ *
50
+ * Can be either:
51
+ * - A function (will be serialized to the client)
52
+ * - A string of JavaScript (inlined directly)
53
+ */
54
+ script: HydrationDirectiveFn | string;
55
+ }
56
+ /**
57
+ * Register a custom hydration directive.
58
+ *
59
+ * @param name - Directive name (e.g. "on:delay", "on:event")
60
+ * @param definition - The directive definition
61
+ */
62
+ export declare function registerHydrationDirective(name: string, definition: HydrationDirectiveDefinition): void;
63
+ /**
64
+ * Unregister a custom hydration directive.
65
+ */
66
+ export declare function unregisterHydrationDirective(name: string): boolean;
67
+ /**
68
+ * Check whether a condition string maps to a registered custom directive.
69
+ */
70
+ export declare function isCustomDirective(condition: string): boolean;
71
+ /**
72
+ * Get a registered directive definition by name.
73
+ */
74
+ export declare function getDirective(name: string): HydrationDirectiveDefinition | undefined;
75
+ /**
76
+ * Get all registered custom directive names.
77
+ */
78
+ export declare function getRegisteredDirectives(): string[];
79
+ /**
80
+ * Serialize a directive's script to an inline-safe string.
81
+ * Used by the SSR renderer to embed the directive logic in the HTML
82
+ * so the client can execute it without an extra network request.
83
+ */
84
+ export declare function serializeDirectiveScript(name: string): string | null;
85
+ /**
86
+ * Clear all registered directives. Useful for testing.
87
+ * @internal
88
+ */
89
+ export declare function clearDirectives(): void;
@@ -0,0 +1 @@
1
+ const e=new Map;export function registerHydrationDirective(t,n){e.has(t)&&console.warn(`[avalon] Hydration directive "${t}" is already registered. Overwriting.`),e.set(t,n)}export function unregisterHydrationDirective(t){return e.delete(t)}export function isCustomDirective(t){return e.has(t)}export function getDirective(t){return e.get(t)}export function getRegisteredDirectives(){return[...e.keys()]}export function serializeDirectiveScript(t){let n=e.get(t);return n?typeof n.script==`string`?n.script:n.script.toString():null}export function clearDirectives(){e.clear()}
@@ -6,7 +6,7 @@ declare global {
6
6
  var __viteDevServer: ViteDevServer | undefined;
7
7
  }
8
8
  /** Supported hydration conditions for island components */
9
- export type HydrationCondition = 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}`;
9
+ export type HydrationCondition = 'on:visible' | 'on:interaction' | 'on:idle' | 'on:client' | `media:${string}` | `on:${string}`;
10
10
  /** Supported framework identifiers (without "unknown") */
11
11
  export type FrameworkId = Exclude<Framework, 'unknown'>;
12
12
  export interface IslandProps {
@@ -14,6 +14,8 @@ export interface IslandProps {
14
14
  src: string;
15
15
  /** Hydration condition */
16
16
  condition?: HydrationCondition;
17
+ /** Optional argument passed to custom hydration directives */
18
+ conditionArg?: string;
17
19
  /** Props to pass to the island component */
18
20
  props?: Record<string, unknown>;
19
21
  /** Children to render inside the island (for SSR) */
@@ -37,7 +39,7 @@ export interface IslandProps {
37
39
  * Uses custom elements instead of div wrappers for cleaner, more semantic markup.
38
40
  * Supports intelligent rendering strategy detection to skip hydration for SSR-only components.
39
41
  */
40
- export default function Island({ src, condition, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, }: IslandProps): JSX.Element;
42
+ export default function Island({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, hydrationData, }: IslandProps): JSX.Element;
41
43
  /**
42
44
  * Universal renderIsland function – auto-detects framework and handles SSR + hydration.
43
45
  *
@@ -54,4 +56,4 @@ export default function Island({ src, condition, props, children, ssr, framework
54
56
  * Error isolation: If SSR fails, returns an error placeholder instead of throwing,
55
57
  * allowing the page to continue rendering other islands.
56
58
  */
57
- export declare function renderIsland({ src, condition, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, }: IslandProps): Promise<JSX.Element>;
59
+ export declare function renderIsland({ src, condition, conditionArg, props, children, ssr, framework, ssrOnly, renderOptions, component: preloadedComponent, }: IslandProps): Promise<JSX.Element>;
@@ -1 +1 @@
1
- import{h as e}from"preact";import{detectFramework as t}from"./framework-detection.js";import{analyzeComponentFile as n,renderComponentSSROnly as r}from"./component-analysis.js";import{loadIntegration as i,detectFrameworkFromPath as a}from"./integration-loader.js";import{addUniversalCSS as o}from"./universal-css-collector.js";import{addUniversalHead as s}from"./universal-head-collector.js";import{getIslandBundlePath as c}from"../build/island-manifest.js";import{isDev as l,devLog as u,devWarn as d,devError as f,logRenderTiming as p}from"../utils/dev-logger.js";function m(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function h(e){let t={};e.renderId&&(t[`data-solid-render-id`]=e.renderId);let n=e.metadata;return n?.tagName&&(t[`data-tag-name`]=n.tagName),t}function g(e,t,n,r){return{"data-condition":t,"data-src":c(e),"data-props":JSON.stringify(n),"data-render-strategy":`hydrate`,...h(r)}}function _(e){return e.startsWith(`<script`)?`script`:e.startsWith(`<style`)?`style`:e.startsWith(`<meta`)?`meta`:e.startsWith(`<link`)?`link`:e.includes(`window._$HY`)||e.includes(`_$HY=`)?`script`:`other`}function v(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function y(e,t,n,r){if(e.css&&o(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=_(i);if(a===`style`){let a=v(i);a&&(u(`${r} Extracting CSS from head <style> tag`),o(a,t,n,e.scopeId));return}s(e.head,t,n,a)}}function b(t){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,props:s,hydrationData:c,children:l}=t,d={id:n,"data-framework":r},f=i?{"data-render-strategy":`ssr-only`}:g(a,o,s,c);r===`lit`&&u(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(c),metadata:c.metadata});let p={...d,...f};return typeof l==`string`?e(`avalon-island`,{...p,dangerouslySetInnerHTML:{__html:l}}):e(`avalon-island`,p,l)}function x(t,n,r,i,a,o,s){return r?e(`avalon-island`,{id:t,"data-render-strategy":`ssr-only`,"data-framework":n}):e(`avalon-island`,{id:t,"data-condition":a,"data-src":c(i),"data-props":JSON.stringify(o),"data-render-strategy":`hydrate`,"data-framework":n,...h(s)})}export default function S({src:e,condition:t=`on:client`,props:n={},children:r,ssr:i=t!==`on:client`,framework:o,ssrOnly:s=!1,renderOptions:c={},hydrationData:l={}}){let f=m(e),p=s||!!c.forceSSROnly,h=o||a(e),g=r!=null&&r!==``;return u(`🔍 [Island Component] ${e}`,{ssr:i,ssrOnly:s,hasChildren:g,framework:o,condition:t}),i&&g?b({islandId:f,detectedFramework:h,shouldSkipHydration:p,src:e,condition:t,props:n,hydrationData:l,children:r}):(i&&!g&&p&&d(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),x(f,h,p,e,t,n,l))}function C(t,n){let r=n instanceof Error?n.message:String(n);return f(`🚨 Island SSR failed for ${t}:`,n),n instanceof Error&&n.stack&&f(`Stack trace:`,n.stack),e(`avalon-island`,{id:m(t),"data-src":c(t),"data-ssr-error":r,"data-render-strategy":`client-only`})}async function w({src:e,condition:t,props:n,children:r,ssr:a,framework:o,ssrOnly:s,renderOptions:c,component:u}){let d=`🏝️ [${e}]`;if(!a||r)return S({src:e,condition:t,props:n,children:r,ssr:a,framework:o,ssrOnly:s,renderOptions:c});let p;try{p=await i(o)}catch(r){return f(`${d} Failed to load ${o} integration:`,r),S({src:e,condition:t,props:n,ssr:!1,framework:o,ssrOnly:s,renderOptions:c})}try{let r=await p.render({component:u??null,props:n,src:e,condition:t,ssrOnly:s,viteServer:globalThis.__viteDevServer,isDev:l()});return y(r,e,o,d),S({src:e,condition:t,props:n,children:r.html,ssr:!0,framework:o,ssrOnly:s,renderOptions:c,hydrationData:s?void 0:r.hydrationData})}catch(r){return f(`${d} Fast path SSR failed:`,r),S({src:e,condition:t,props:n,ssr:!1,framework:o,ssrOnly:s,renderOptions:c})}}async function T(e,t,r,i){if(t||r.detectScripts===!1)return t;try{let t=await n(e,r);if(t.decision.warnings?.length)for(let e of t.decision.warnings)d(`${i} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return d(`${i} Component analysis failed:`,e),t}}async function E(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?t(e):`unknown`}async function D(e,t,n,r,i,a,o){let s=await E(e),c=s,u=await(await O(s,a)).render({component:o??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:l()});return y(u,e,s,a),S({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:c,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function O(e,t){try{u(`${t} Loading integration for framework: ${e}`);let n=await i(e);return u(`${t} ✅ Integration loaded successfully`),n}catch(n){throw f(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:e,condition:t=`on:client`,props:n={},children:r,ssr:i=t!==`on:client`,framework:a,ssrOnly:o=!1,renderOptions:s={},component:c}){let u=l()?performance.now():0,d=`🏝️ [${e}]`;try{return o&&!i&&(i=!0),a?await w({src:e,condition:t,props:n,children:r,ssr:i,framework:a,ssrOnly:o,renderOptions:s,component:c}):await k({src:e,condition:t,props:n,children:r,ssr:i,ssrOnly:o,renderOptions:s,logPrefix:d,component:c})}catch(t){return C(e,t)}finally{l()&&p(e,performance.now()-u)}}async function k(e){let{src:t,condition:n,props:r,children:i,ssr:a,ssrOnly:o,renderOptions:s,logPrefix:c,component:l}=e;if(u(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:a,ssrOnly:o,hasChildren:!!i,condition:n}),await T(t,o,s,c))return A(t,n,r,i,a,s,c);if(!a||i)return S({src:t,condition:n,props:r,children:i,ssr:a,renderOptions:s});try{return await D(t,n,r,o,s,c,l)}catch(e){let i=await E(t);return f(`${c} Framework rendering failed:`,e),S({src:t,condition:n,props:r,ssr:!1,framework:i,renderOptions:s})}}function A(e,t,n,i,a,o,s){return a&&!i?r({src:e,condition:t,props:n,renderOptions:o}).catch(r=>(f(`${s} SSR failed for SSR-only component:`,r),S({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:o}))):S({src:e,condition:t,props:n,children:i,ssr:a,ssrOnly:!0,renderOptions:o})}
1
+ import{h as e}from"preact";import{detectFramework as t}from"./framework-detection.js";import{analyzeComponentFile as n,renderComponentSSROnly as r}from"./component-analysis.js";import{loadIntegration as i,detectFrameworkFromPath as a}from"./integration-loader.js";import{addUniversalCSS as o}from"./universal-css-collector.js";import{addUniversalHead as s}from"./universal-head-collector.js";import{getIslandBundlePath as c}from"../build/island-manifest.js";import{isDev as l,devLog as u,devWarn as d,devError as f,logRenderTiming as p}from"../utils/dev-logger.js";import{isCustomDirective as m,serializeDirectiveScript as h}from"./hydration-directives.js";function g(e){return`island-${e.replaceAll(/[^a-zA-Z0-9]/g,`-`)}`}function _(e){let t={};e.renderId&&(t[`data-solid-render-id`]=e.renderId);let n=e.metadata;return n?.tagName&&(t[`data-tag-name`]=n.tagName),t}function v(e,t,n,r,i){let a={"data-condition":t,"data-src":c(e),"data-props":JSON.stringify(n),"data-render-strategy":`hydrate`,..._(r)};if(m(t)){a[`data-custom-directive`]=t;let e=h(t);e&&(a[`data-directive-script`]=e)}return i&&(a[`data-condition-arg`]=i),a}function y(e){return e.startsWith(`<script`)?`script`:e.startsWith(`<style`)?`style`:e.startsWith(`<meta`)?`meta`:e.startsWith(`<link`)?`link`:e.includes(`window._$HY`)||e.includes(`_$HY=`)?`script`:`other`}function b(e){let t=e.match(/<style[^>]*>([\s\S]*?)<\/style>/i);return t?t[1].trim():null}function x(e,t,n,r){if(e.css&&o(e.css,t,n,e.scopeId),e.head){let i=e.head.trim(),a=y(i);if(a===`style`){let a=b(i);a&&(u(`${r} Extracting CSS from head <style> tag`),o(a,t,n,e.scopeId));return}s(e.head,t,n,a)}}function S(t){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,conditionArg:s,props:c,hydrationData:l,children:d}=t,f={id:n,"data-framework":r},p=i?{"data-render-strategy":`ssr-only`}:v(a,o,c,l,s);r===`lit`&&u(`🔍 [Island Component] ${a} - Lit hydration data:`,{hydrationDataKeys:Object.keys(l),metadata:l.metadata});let m={...f,...p};return typeof d==`string`?e(`avalon-island`,{...m,dangerouslySetInnerHTML:{__html:d}}):e(`avalon-island`,m,d)}function C(t){let{islandId:n,detectedFramework:r,shouldSkipHydration:i,src:a,condition:o,props:s,hydrationData:l,conditionArg:u}=t;if(i)return e(`avalon-island`,{id:n,"data-render-strategy":`ssr-only`,"data-framework":r});let d={id:n,"data-condition":o,"data-src":c(a),"data-props":JSON.stringify(s),"data-render-strategy":`hydrate`,"data-framework":r,..._(l)};if(m(o)){d[`data-custom-directive`]=o;let e=h(o);e&&(d[`data-directive-script`]=e)}return u&&(d[`data-condition-arg`]=u),e(`avalon-island`,d)}export default function w({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:o=t!==`on:client`,framework:s,ssrOnly:c=!1,renderOptions:l={},hydrationData:f={}}){let p=g(e),m=c||!!l.forceSSROnly,h=s||a(e),_=i!=null&&i!==``;return u(`🔍 [Island Component] ${e}`,{ssr:o,ssrOnly:c,hasChildren:_,framework:s,condition:t}),o&&_?S({islandId:p,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,conditionArg:n,props:r,hydrationData:f,children:i}):(o&&!_&&m&&d(`${e}: SSR-only component has no rendered content. This may indicate a rendering error.`),C({islandId:p,detectedFramework:h,shouldSkipHydration:m,src:e,condition:t,props:r,hydrationData:f,conditionArg:n}))}function T(t,n){let r=n instanceof Error?n.message:String(n);return f(`🚨 Island SSR failed for ${t}:`,n),n instanceof Error&&n.stack&&f(`Stack trace:`,n.stack),e(`avalon-island`,{id:g(t),"data-src":c(t),"data-ssr-error":r,"data-render-strategy":`client-only`})}async function E({src:e,condition:t,conditionArg:n,props:r,children:a,ssr:o,framework:s,ssrOnly:c,renderOptions:u,component:d}){let p=`🏝️ [${e}]`;if(!o||a)return w({src:e,condition:t,conditionArg:n,props:r,children:a,ssr:o,framework:s,ssrOnly:c,renderOptions:u});let m;try{m=await i(s)}catch(i){return f(`${p} Failed to load ${s} integration:`,i),w({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}try{let i=await m.render({component:d??null,props:r,src:e,condition:t,ssrOnly:c,viteServer:globalThis.__viteDevServer,isDev:l()});return x(i,e,s,p),w({src:e,condition:t,conditionArg:n,props:r,children:i.html,ssr:!0,framework:s,ssrOnly:c,renderOptions:u,hydrationData:c?void 0:i.hydrationData})}catch(i){return f(`${p} Fast path SSR failed:`,i),w({src:e,condition:t,conditionArg:n,props:r,ssr:!1,framework:s,ssrOnly:c,renderOptions:u})}}async function D(e,t,r,i){if(t||r.detectScripts===!1)return t;try{let t=await n(e,r);if(t.decision.warnings?.length)for(let e of t.decision.warnings)d(`${i} Analysis warning: ${e}`);return!t.decision.shouldHydrate}catch(e){return d(`${i} Component analysis failed:`,e),t}}async function O(e){return e.endsWith(`.vue`)?`vue`:e.endsWith(`.svelte`)?`svelte`:e.endsWith(`.tsx`)||e.endsWith(`.jsx`)||e.endsWith(`.ts`)||e.endsWith(`.js`)?t(e):`unknown`}async function k(e,t,n,r,i,a,o){let s=await O(e),c=s,u=await(await A(s,a)).render({component:o??null,props:n,src:e,condition:t,ssrOnly:r,viteServer:globalThis.__viteDevServer,isDev:l()});return x(u,e,s,a),w({src:e,condition:t,props:n,children:u.html,ssr:!0,framework:c,ssrOnly:r,renderOptions:i,hydrationData:r?void 0:u.hydrationData})}async function A(e,t){try{u(`${t} Loading integration for framework: ${e}`);let n=await i(e);return u(`${t} ✅ Integration loaded successfully`),n}catch(n){throw f(`${t} Failed to load ${e} integration:`,n),Error(`Failed to load integration for framework '${e}'. Make sure @useavalon/${e} is installed.\nInstall it with: deno add @useavalon/${e}`,{cause:n})}}export async function renderIsland({src:e,condition:t=`on:client`,conditionArg:n,props:r={},children:i,ssr:a=t!==`on:client`,framework:o,ssrOnly:s=!1,renderOptions:c={},component:u}){let d=l()?performance.now():0,f=`🏝️ [${e}]`;try{return s&&!a&&(a=!0),o?await E({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,framework:o,ssrOnly:s,renderOptions:c,component:u}):await j({src:e,condition:t,conditionArg:n,props:r,children:i,ssr:a,ssrOnly:s,renderOptions:c,logPrefix:f,component:u})}catch(t){return T(e,t)}finally{l()&&p(e,performance.now()-d)}}async function j(e){let{src:t,condition:n,conditionArg:r,props:i,children:a,ssr:o,ssrOnly:s,renderOptions:c,logPrefix:l,component:d}=e;if(u(`🔍 [renderIsland] ${t} - Starting render (slow path)`,{ssr:o,ssrOnly:s,hasChildren:!!a,condition:n}),await D(t,s,c,l))return M(t,n,i,a,o,c,l);if(!o||a)return w({src:t,condition:n,conditionArg:r,props:i,children:a,ssr:o,renderOptions:c});try{return await k(t,n,i,s,c,l,d)}catch(e){let a=await O(t);return f(`${l} Framework rendering failed:`,e),w({src:t,condition:n,conditionArg:r,props:i,ssr:!1,framework:a,renderOptions:c})}}function M(e,t,n,i,a,o,s){return a&&!i?r({src:e,condition:t,props:n,renderOptions:o}).catch(r=>(f(`${s} SSR failed for SSR-only component:`,r),w({src:e,condition:t,props:n,ssr:!1,ssrOnly:!0,renderOptions:o}))):w({src:e,condition:t,props:n,children:i,ssr:a,ssrOnly:!0,renderOptions:o})}
@@ -12,8 +12,10 @@ export type Framework = "solid" | "vue" | "svelte" | "preact" | "react" | "lit"
12
12
  export interface IslandProps {
13
13
  /** Path to the island component (e.g., "/islands/Counter.tsx") */
14
14
  src: string;
15
- /** Hydration condition */
16
- condition?: "on:visible" | "on:interaction" | "on:idle" | "on:client" | `media:${string}`;
15
+ /** Hydration condition (built-in or custom directive name) */
16
+ condition?: "on:visible" | "on:interaction" | "on:idle" | "on:client" | `media:${string}` | `on:${string}`;
17
+ /** Optional argument passed to custom hydration directives */
18
+ conditionArg?: string;
17
19
  /** Props to pass to the island component */
18
20
  props?: Record<string, unknown>;
19
21
  /** Children to render inside the island (for SSR) */