@ox-content/islands 0.17.0 → 1.1.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.
@@ -0,0 +1,2 @@
1
+ import { a as HydrateFunction, c as IslandController, i as ComponentRegistry, l as IslandInstance, n as initIslands, o as InitIslandsOptions, r as isIslandsSupported, s as IslandConfig, t as createDeferredInit, u as LoadStrategy } from "./runtime.mjs";
2
+ export { type ComponentRegistry, type HydrateFunction, type InitIslandsOptions, type IslandConfig, type IslandController, type IslandInstance, type LoadStrategy, createDeferredInit, initIslands, isIslandsSupported };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { createDeferredInit, initIslands, isIslandsSupported } from "./runtime.mjs";
2
+ export { createDeferredInit, initIslands, isIslandsSupported };
@@ -78,7 +78,6 @@ interface IslandController {
78
78
  /** Destroy all islands and cleanup */
79
79
  destroy: () => void;
80
80
  }
81
- //# sourceMappingURL=types.d.ts.map
82
81
  //#endregion
83
82
  //#region src/runtime.d.ts
84
83
  /**
@@ -171,7 +170,6 @@ declare function createDeferredInit(options?: InitIslandsOptions): (hydrate: Hyd
171
170
  * Check if islands are supported in the current environment.
172
171
  */
173
172
  declare function isIslandsSupported(): boolean;
174
- //# sourceMappingURL=runtime.d.ts.map
175
173
  //#endregion
176
- export { type ComponentRegistry, type HydrateFunction, type InitIslandsOptions, type IslandConfig, type IslandController, type IslandInstance, type LoadStrategy, createDeferredInit, initIslands, isIslandsSupported };
177
- //# sourceMappingURL=index-Bc2Aw38x.d.ts.map
174
+ export { HydrateFunction as a, IslandController as c, ComponentRegistry as i, IslandInstance as l, initIslands as n, InitIslandsOptions as o, isIslandsSupported as r, IslandConfig as s, createDeferredInit as t, LoadStrategy as u };
175
+ //# sourceMappingURL=runtime.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/types.ts","../src/runtime.ts"],"mappings":";;AAcA;;;;;AAKA;;;;;;;KALY,YAAA;;;;UAKK,YAAA;EAUF;EARb,EAAA;EAiByB;EAfzB,SAAA;EAiBa;EAfb,IAAA,EAAM,YAAA;EAcN;EAZA,UAAA;EAaA;EAXA,KAAA,EAAO,MAAA;AAAA;AAiBT;;;;;AAKA;AALA,KARY,eAAA,IACV,OAAA,EAAS,WAAA,EACT,KAAA,EAAO,MAAA;;;;KAMG,iBAAA,GAAoB,GAAA,SAAY,eAAA;;;;UAK3B,kBAAA;EAc2D;EAZ1E,UAAA;EAAA;EAEA,SAAA;EAEA;EAAA,WAAA;EAIA;EAFA,QAAA;EAEkB;EAAlB,cAAA,IAAkB,OAAA,EAAS,WAAA,EAAa,MAAA,EAAQ,YAAA;EAAR;EAExC,YAAA,IAAgB,OAAA,EAAS,WAAA,EAAa,MAAA,EAAQ,YAAA;EAArB;EAEzB,cAAA,IAAkB,OAAA,EAAS,WAAA,EAAa,MAAA,EAAQ,YAAA,EAAc,KAAA,EAAO,KAAA;AAAA;;;;UAMtD,cAAA;EACf,OAAA,EAAS,WAAA;EACT,MAAA,EAAQ,YAAA;EACR,OAAA;EACA,QAAA;AAAA;;AAJF;;UAUiB,gBAAA;EARK;EAUpB,SAAA,EAAW,cAAA;EAXF;EAaT,OAAA,GAAU,OAAA,EAAS,WAAA;EAZX;EAcR,OAAA;AAAA;;;;;;;;;;;;;;;AAjDF;;;;;;;;;;AAQA;;;;;AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoBA;;;;iBC+GgB,WAAA,CACd,OAAA,EAAS,eAAA,EACT,OAAA,GAAU,kBAAA,GACT,gBAAA;;;;;;;;ADxGH;;;;;;;;;;;iBCiQgB,kBAAA,CACd,OAAA,GAAU,kBAAA,IACR,OAAA,EAAS,eAAA,KAAoB,gBAAA;;;;iBAOjB,kBAAA,CAAA"}
@@ -255,7 +255,7 @@ function createDeferredInit(options) {
255
255
  function isIslandsSupported() {
256
256
  return typeof window !== "undefined" && typeof document !== "undefined" && typeof IntersectionObserver !== "undefined" && typeof MutationObserver !== "undefined";
257
257
  }
258
-
259
258
  //#endregion
260
259
  export { createDeferredInit, initIslands, isIslandsSupported };
261
- //# sourceMappingURL=index.js.map
260
+
261
+ //# sourceMappingURL=runtime.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Island Architecture Runtime\n *\n * Framework-agnostic Island controller using pure Vanilla JavaScript.\n * No framework dependencies - works with Vue, React, Svelte, or plain JS.\n */\n\nimport type {\n HydrateFunction,\n InitIslandsOptions,\n IslandConfig,\n IslandInstance,\n IslandController,\n LoadStrategy,\n} from \"./types\";\n\nconst defaultOptions: Required<\n Omit<InitIslandsOptions, \"onHydrateStart\" | \"onHydrateEnd\" | \"onHydrateError\">\n> &\n InitIslandsOptions = {\n rootMargin: \"200px\",\n threshold: 0,\n idleTimeout: 200,\n selector: \"[data-ox-island]\",\n};\n\n/**\n * Parse island configuration from element attributes.\n */\nfunction parseIslandConfig(element: HTMLElement): IslandConfig {\n const component = element.dataset.oxIsland || \"\";\n const load = (element.dataset.oxLoad as LoadStrategy) || \"eager\";\n const mediaQuery = element.dataset.oxMedia;\n\n let props: Record<string, unknown> = {};\n try {\n const propsJson = element.dataset.oxProps;\n if (propsJson) {\n props = JSON.parse(propsJson);\n }\n } catch (e) {\n console.warn(\"[ox-islands] Failed to parse props for\", element, e);\n }\n\n return {\n id: element.id || `island-${Math.random().toString(36).slice(2, 9)}`,\n component,\n load,\n mediaQuery,\n props,\n };\n}\n\n/**\n * Observe element visibility using IntersectionObserver.\n */\nfunction observeVisibility(\n element: HTMLElement,\n callback: () => void,\n options: { rootMargin: string; threshold: number },\n): () => void {\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0].isIntersecting) {\n observer.disconnect();\n callback();\n }\n },\n {\n rootMargin: options.rootMargin,\n threshold: options.threshold,\n },\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n}\n\n/**\n * Observe media query changes.\n */\nfunction observeMedia(query: string, callback: () => void): () => void {\n const mql = matchMedia(query);\n\n if (mql.matches) {\n callback();\n return () => {};\n }\n\n const handler = () => {\n if (mql.matches) {\n mql.removeEventListener(\"change\", handler);\n callback();\n }\n };\n\n mql.addEventListener(\"change\", handler);\n\n return () => mql.removeEventListener(\"change\", handler);\n}\n\n/**\n * Schedule callback for browser idle time.\n */\nfunction scheduleIdle(callback: () => void, timeout: number): () => void {\n if (\"requestIdleCallback\" in window) {\n const id = requestIdleCallback(callback, { timeout });\n return () => cancelIdleCallback(id);\n }\n\n // Fallback for Safari and older browsers\n const id = setTimeout(callback, timeout);\n return () => clearTimeout(id);\n}\n\n/**\n * Initialize islands with a hydration function.\n *\n * This is the main entry point for the island system.\n * Pass a hydrate function that knows how to mount your components.\n *\n * @example Vue\n * ```ts\n * import { initIslands } from '@ox-content/islands';\n * import { createApp, h } from 'vue';\n * import Counter from './Counter.vue';\n *\n * const components = { Counter };\n *\n * initIslands((el, props) => {\n * const name = el.dataset.oxIsland!;\n * const Component = components[name];\n * if (!Component) return;\n *\n * const app = createApp({ render: () => h(Component, props) });\n * app.mount(el);\n *\n * return () => app.unmount();\n * });\n * ```\n *\n * @example React\n * ```ts\n * import { initIslands } from '@ox-content/islands';\n * import { createRoot } from 'react-dom/client';\n * import Counter from './Counter';\n *\n * const components = { Counter };\n *\n * initIslands((el, props) => {\n * const name = el.dataset.oxIsland!;\n * const Component = components[name];\n * if (!Component) return;\n *\n * const root = createRoot(el);\n * root.render(<Component {...props} />);\n *\n * return () => root.unmount();\n * });\n * ```\n *\n * @example Vanilla JS\n * ```ts\n * import { initIslands } from '@ox-content/islands';\n *\n * initIslands((el, props) => {\n * const name = el.dataset.oxIsland!;\n *\n * if (name === 'Counter') {\n * let count = props.initial || 0;\n * const button = el.querySelector('button')!;\n * const handler = () => {\n * count++;\n * button.textContent = String(count);\n * };\n * button.addEventListener('click', handler);\n * return () => button.removeEventListener('click', handler);\n * }\n * });\n * ```\n */\nexport function initIslands(\n hydrate: HydrateFunction,\n options?: InitIslandsOptions,\n): IslandController {\n const opts = { ...defaultOptions, ...options };\n const instances: IslandInstance[] = [];\n const cleanups: (() => void)[] = [];\n\n /**\n * Hydrate a single island element.\n */\n function hydrateIsland(element: HTMLElement, config: IslandConfig): void {\n // Find existing instance\n const instance = instances.find((i) => i.element === element);\n if (instance?.hydrated) return;\n\n try {\n opts.onHydrateStart?.(element, config);\n\n // Mark as loading\n element.classList.add(\"ox-island-loading\");\n\n // Call the hydrate function\n const cleanup = hydrate(element, config.props);\n\n // Mark as hydrated\n element.dataset.oxHydrated = \"true\";\n element.classList.remove(\"ox-island-loading\");\n\n // Update instance\n if (instance) {\n instance.cleanup = cleanup || undefined;\n instance.hydrated = true;\n } else {\n instances.push({\n element,\n config,\n cleanup: cleanup || undefined,\n hydrated: true,\n });\n }\n\n opts.onHydrateEnd?.(element, config);\n } catch (error) {\n element.classList.remove(\"ox-island-loading\");\n element.classList.add(\"ox-island-error\");\n element.dataset.oxError = error instanceof Error ? error.message : String(error);\n\n opts.onHydrateError?.(\n element,\n config,\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n /**\n * Schedule hydration based on load strategy.\n */\n function scheduleHydration(element: HTMLElement, config: IslandConfig): void {\n switch (config.load) {\n case \"eager\":\n hydrateIsland(element, config);\n break;\n\n case \"idle\": {\n const cancel = scheduleIdle(() => hydrateIsland(element, config), opts.idleTimeout);\n cleanups.push(cancel);\n break;\n }\n\n case \"visible\": {\n const cancel = observeVisibility(element, () => hydrateIsland(element, config), {\n rootMargin: opts.rootMargin,\n threshold: opts.threshold,\n });\n cleanups.push(cancel);\n break;\n }\n\n case \"media\": {\n if (config.mediaQuery) {\n const cancel = observeMedia(config.mediaQuery, () => hydrateIsland(element, config));\n cleanups.push(cancel);\n } else {\n // No media query specified, fall back to eager\n hydrateIsland(element, config);\n }\n break;\n }\n\n default:\n hydrateIsland(element, config);\n }\n }\n\n // Find and process all island elements\n const elements = document.querySelectorAll<HTMLElement>(opts.selector);\n\n elements.forEach((element) => {\n const config = parseIslandConfig(element);\n\n // Track instance\n instances.push({\n element,\n config,\n hydrated: false,\n });\n\n // Schedule hydration\n scheduleHydration(element, config);\n });\n\n // Return controller\n return {\n instances,\n\n hydrate(element: HTMLElement): void {\n const config = parseIslandConfig(element);\n hydrateIsland(element, config);\n },\n\n destroy(): void {\n // Cancel pending hydrations\n cleanups.forEach((cleanup) => cleanup());\n cleanups.length = 0;\n\n // Cleanup hydrated instances\n instances.forEach((instance) => {\n if (instance.cleanup) {\n instance.cleanup();\n }\n });\n instances.length = 0;\n },\n };\n}\n\n/**\n * Create a deferred hydration wrapper.\n *\n * Returns a function that can be called to hydrate islands later.\n * Useful for frameworks that need to register components first.\n *\n * @example\n * ```ts\n * const deferredInit = createDeferredInit();\n *\n * // Later, after components are ready\n * const components = await loadComponents();\n * deferredInit((el, props) => {\n * const Component = components[el.dataset.oxIsland!];\n * // ...\n * });\n * ```\n */\nexport function createDeferredInit(\n options?: InitIslandsOptions,\n): (hydrate: HydrateFunction) => IslandController {\n return (hydrate: HydrateFunction) => initIslands(hydrate, options);\n}\n\n/**\n * Check if islands are supported in the current environment.\n */\nexport function isIslandsSupported(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof document !== \"undefined\" &&\n typeof IntersectionObserver !== \"undefined\" &&\n typeof MutationObserver !== \"undefined\"\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { n as initIslands, r as isIslandsSupported, t as createDeferredInit } from "./runtime.mjs";
2
+ export { createDeferredInit, initIslands, isIslandsSupported };
package/package.json CHANGED
@@ -1,35 +1,13 @@
1
1
  {
2
2
  "name": "@ox-content/islands",
3
- "version": "0.17.0",
3
+ "version": "1.1.0",
4
4
  "description": "Framework-agnostic Island Architecture for ox-content",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "import": "./dist/index.js",
11
- "types": "./dist/index.d.ts"
12
- },
13
- "./runtime": {
14
- "import": "./dist/runtime.js",
15
- "types": "./dist/runtime.d.ts"
16
- }
17
- },
18
- "files": [
19
- "dist"
20
- ],
21
- "devDependencies": {
22
- "@types/node": "^22.0.0",
23
- "@typescript/native-preview": "^7.0.0-dev.20250601",
24
- "tsdown": "^0.12.0",
25
- "typescript": "^5.7.0"
26
- },
27
5
  "keywords": [
28
- "islands",
6
+ "framework-agnostic",
29
7
  "island-architecture",
30
- "partial-hydration",
8
+ "islands",
31
9
  "ox-content",
32
- "framework-agnostic"
10
+ "partial-hydration"
33
11
  ],
34
12
  "license": "MIT",
35
13
  "author": "ubugeeei",
@@ -38,12 +16,34 @@
38
16
  "url": "https://github.com/ubugeeei/ox-content.git",
39
17
  "directory": "npm/ox-content-islands"
40
18
  },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.mjs",
24
+ "types": "./dist/index.d.mts",
25
+ "exports": {
26
+ ".": {
27
+ "import": "./dist/index.mjs",
28
+ "types": "./dist/index.d.mts"
29
+ },
30
+ "./runtime": {
31
+ "import": "./dist/runtime.mjs",
32
+ "types": "./dist/runtime.d.mts"
33
+ }
34
+ },
41
35
  "publishConfig": {
42
36
  "access": "public"
43
37
  },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0",
40
+ "@typescript/native-preview": "^7.0.0-dev.20250601",
41
+ "typescript": "^5.7.0",
42
+ "vite-plus": "0.1.11"
43
+ },
44
44
  "scripts": {
45
- "build": "tsdown",
46
- "dev": "tsdown --watch",
45
+ "build": "vp pack",
46
+ "dev": "vp pack --watch",
47
47
  "typecheck": "tsgo --noEmit"
48
48
  }
49
49
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-Bc2Aw38x.d.ts","names":[],"sources":["../src/types.ts","../src/runtime.ts"],"sourcesContent":[],"mappings":";;AAcA;AAKA;;;;;AAmBA;;;;;AAQA;AAA6B,KAhCjB,YAAA,GAgCiB,OAAA,GAAA,MAAA,GAAA,SAAA,GAAA,OAAA;;;;AAKZ,UAhCA,YAAA,CAgCkB;EAAA;MAUN,MAAA;;WAEF,EAAA,MAAA;;MAEE,EAxCrB,YAwCqB;;YAA0C,CAAA,EAAA,MAAA;EAAK;EAM3D,KAAA,EA1CR,MA0CQ,CAAA,MAAc,EAAA,OAAA,CAAA;;;;;AAU/B;;;AAIqB,KA/CT,eAAA,GA+CS,CAAA,OAAA,EA9CV,WA8CU,EAAA,KAAA,EA7CZ,MA6CY,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,GAAA,IAAA,GAAA,CAAA,GAAA,GAAA,IAAA,CAAA;;;;KAvCT,iBAAA,GAAoB,YAAY;ACwI5C;;;AAEY,UDrIK,kBAAA,CCqIL;;EACO,UAAA,CAAA,EAAA,MAAA;EAyJH;EAAkB,SAAA,CAAA,EAAA,MAAA;;aAErB,CAAA,EAAA,MAAA;;EAAoC,QAAA,CAAA,EAAA,MAAA;EAOjC;6BD9Ra,qBAAqB;;2BAEvB,qBAAqB;;6BAEnB,qBAAqB,qBAAqB;;;;;UAMtD,cAAA;WACN;UACD;;;;;;;UAQO,gBAAA;;aAEJ;;qBAEQ;;;;;;;AA/CrB;;;;;AAQA;;;;;AAKA;;;;;;;;;;AAoBA;;;;;AAUA;;;;;;;;ACqGA;;;;;;AA4JA;;;;;;AASA;;;;;;;;;;;;;;;;;;;;;iBArKgB,WAAA,UACL,2BACC,qBACT;;;;;;;;;;;;;;;;;;;iBAyJa,kBAAA,WACJ,+BACC,oBAAoB;;;;iBAOjB,kBAAA,CAAA"}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["/**\n * Island Architecture Runtime\n *\n * Framework-agnostic Island controller using pure Vanilla JavaScript.\n * No framework dependencies - works with Vue, React, Svelte, or plain JS.\n */\n\nimport type {\n HydrateFunction,\n InitIslandsOptions,\n IslandConfig,\n IslandInstance,\n IslandController,\n LoadStrategy,\n} from \"./types\";\n\nconst defaultOptions: Required<\n Omit<InitIslandsOptions, \"onHydrateStart\" | \"onHydrateEnd\" | \"onHydrateError\">\n> &\n InitIslandsOptions = {\n rootMargin: \"200px\",\n threshold: 0,\n idleTimeout: 200,\n selector: \"[data-ox-island]\",\n};\n\n/**\n * Parse island configuration from element attributes.\n */\nfunction parseIslandConfig(element: HTMLElement): IslandConfig {\n const component = element.dataset.oxIsland || \"\";\n const load = (element.dataset.oxLoad as LoadStrategy) || \"eager\";\n const mediaQuery = element.dataset.oxMedia;\n\n let props: Record<string, unknown> = {};\n try {\n const propsJson = element.dataset.oxProps;\n if (propsJson) {\n props = JSON.parse(propsJson);\n }\n } catch (e) {\n console.warn(\"[ox-islands] Failed to parse props for\", element, e);\n }\n\n return {\n id: element.id || `island-${Math.random().toString(36).slice(2, 9)}`,\n component,\n load,\n mediaQuery,\n props,\n };\n}\n\n/**\n * Observe element visibility using IntersectionObserver.\n */\nfunction observeVisibility(\n element: HTMLElement,\n callback: () => void,\n options: { rootMargin: string; threshold: number },\n): () => void {\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0].isIntersecting) {\n observer.disconnect();\n callback();\n }\n },\n {\n rootMargin: options.rootMargin,\n threshold: options.threshold,\n },\n );\n\n observer.observe(element);\n\n return () => observer.disconnect();\n}\n\n/**\n * Observe media query changes.\n */\nfunction observeMedia(query: string, callback: () => void): () => void {\n const mql = matchMedia(query);\n\n if (mql.matches) {\n callback();\n return () => {};\n }\n\n const handler = () => {\n if (mql.matches) {\n mql.removeEventListener(\"change\", handler);\n callback();\n }\n };\n\n mql.addEventListener(\"change\", handler);\n\n return () => mql.removeEventListener(\"change\", handler);\n}\n\n/**\n * Schedule callback for browser idle time.\n */\nfunction scheduleIdle(callback: () => void, timeout: number): () => void {\n if (\"requestIdleCallback\" in window) {\n const id = requestIdleCallback(callback, { timeout });\n return () => cancelIdleCallback(id);\n }\n\n // Fallback for Safari and older browsers\n const id = setTimeout(callback, timeout);\n return () => clearTimeout(id);\n}\n\n/**\n * Initialize islands with a hydration function.\n *\n * This is the main entry point for the island system.\n * Pass a hydrate function that knows how to mount your components.\n *\n * @example Vue\n * ```ts\n * import { initIslands } from '@ox-content/islands';\n * import { createApp, h } from 'vue';\n * import Counter from './Counter.vue';\n *\n * const components = { Counter };\n *\n * initIslands((el, props) => {\n * const name = el.dataset.oxIsland!;\n * const Component = components[name];\n * if (!Component) return;\n *\n * const app = createApp({ render: () => h(Component, props) });\n * app.mount(el);\n *\n * return () => app.unmount();\n * });\n * ```\n *\n * @example React\n * ```ts\n * import { initIslands } from '@ox-content/islands';\n * import { createRoot } from 'react-dom/client';\n * import Counter from './Counter';\n *\n * const components = { Counter };\n *\n * initIslands((el, props) => {\n * const name = el.dataset.oxIsland!;\n * const Component = components[name];\n * if (!Component) return;\n *\n * const root = createRoot(el);\n * root.render(<Component {...props} />);\n *\n * return () => root.unmount();\n * });\n * ```\n *\n * @example Vanilla JS\n * ```ts\n * import { initIslands } from '@ox-content/islands';\n *\n * initIslands((el, props) => {\n * const name = el.dataset.oxIsland!;\n *\n * if (name === 'Counter') {\n * let count = props.initial || 0;\n * const button = el.querySelector('button')!;\n * const handler = () => {\n * count++;\n * button.textContent = String(count);\n * };\n * button.addEventListener('click', handler);\n * return () => button.removeEventListener('click', handler);\n * }\n * });\n * ```\n */\nexport function initIslands(\n hydrate: HydrateFunction,\n options?: InitIslandsOptions,\n): IslandController {\n const opts = { ...defaultOptions, ...options };\n const instances: IslandInstance[] = [];\n const cleanups: (() => void)[] = [];\n\n /**\n * Hydrate a single island element.\n */\n function hydrateIsland(element: HTMLElement, config: IslandConfig): void {\n // Find existing instance\n const instance = instances.find((i) => i.element === element);\n if (instance?.hydrated) return;\n\n try {\n opts.onHydrateStart?.(element, config);\n\n // Mark as loading\n element.classList.add(\"ox-island-loading\");\n\n // Call the hydrate function\n const cleanup = hydrate(element, config.props);\n\n // Mark as hydrated\n element.dataset.oxHydrated = \"true\";\n element.classList.remove(\"ox-island-loading\");\n\n // Update instance\n if (instance) {\n instance.cleanup = cleanup || undefined;\n instance.hydrated = true;\n } else {\n instances.push({\n element,\n config,\n cleanup: cleanup || undefined,\n hydrated: true,\n });\n }\n\n opts.onHydrateEnd?.(element, config);\n } catch (error) {\n element.classList.remove(\"ox-island-loading\");\n element.classList.add(\"ox-island-error\");\n element.dataset.oxError = error instanceof Error ? error.message : String(error);\n\n opts.onHydrateError?.(\n element,\n config,\n error instanceof Error ? error : new Error(String(error)),\n );\n }\n }\n\n /**\n * Schedule hydration based on load strategy.\n */\n function scheduleHydration(element: HTMLElement, config: IslandConfig): void {\n switch (config.load) {\n case \"eager\":\n hydrateIsland(element, config);\n break;\n\n case \"idle\": {\n const cancel = scheduleIdle(() => hydrateIsland(element, config), opts.idleTimeout);\n cleanups.push(cancel);\n break;\n }\n\n case \"visible\": {\n const cancel = observeVisibility(element, () => hydrateIsland(element, config), {\n rootMargin: opts.rootMargin,\n threshold: opts.threshold,\n });\n cleanups.push(cancel);\n break;\n }\n\n case \"media\": {\n if (config.mediaQuery) {\n const cancel = observeMedia(config.mediaQuery, () => hydrateIsland(element, config));\n cleanups.push(cancel);\n } else {\n // No media query specified, fall back to eager\n hydrateIsland(element, config);\n }\n break;\n }\n\n default:\n hydrateIsland(element, config);\n }\n }\n\n // Find and process all island elements\n const elements = document.querySelectorAll<HTMLElement>(opts.selector);\n\n elements.forEach((element) => {\n const config = parseIslandConfig(element);\n\n // Track instance\n instances.push({\n element,\n config,\n hydrated: false,\n });\n\n // Schedule hydration\n scheduleHydration(element, config);\n });\n\n // Return controller\n return {\n instances,\n\n hydrate(element: HTMLElement): void {\n const config = parseIslandConfig(element);\n hydrateIsland(element, config);\n },\n\n destroy(): void {\n // Cancel pending hydrations\n cleanups.forEach((cleanup) => cleanup());\n cleanups.length = 0;\n\n // Cleanup hydrated instances\n instances.forEach((instance) => {\n if (instance.cleanup) {\n instance.cleanup();\n }\n });\n instances.length = 0;\n },\n };\n}\n\n/**\n * Create a deferred hydration wrapper.\n *\n * Returns a function that can be called to hydrate islands later.\n * Useful for frameworks that need to register components first.\n *\n * @example\n * ```ts\n * const deferredInit = createDeferredInit();\n *\n * // Later, after components are ready\n * const components = await loadComponents();\n * deferredInit((el, props) => {\n * const Component = components[el.dataset.oxIsland!];\n * // ...\n * });\n * ```\n */\nexport function createDeferredInit(\n options?: InitIslandsOptions,\n): (hydrate: HydrateFunction) => IslandController {\n return (hydrate: HydrateFunction) => initIslands(hydrate, options);\n}\n\n/**\n * Check if islands are supported in the current environment.\n */\nexport function isIslandsSupported(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof document !== \"undefined\" &&\n typeof IntersectionObserver !== \"undefined\" &&\n typeof MutationObserver !== \"undefined\"\n );\n}\n"],"mappings":"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"}