@voltro/web 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ssr.d.ts CHANGED
@@ -2,11 +2,99 @@ import { ComponentType } from 'react';
2
2
  import { PipeableStream } from 'react-dom/server';
3
3
  import { ReactNode } from 'react';
4
4
 
5
+ /**
6
+ * Throw unless this page can actually stream. Verified against React 19.2.7
7
+ * rather than assumed — each rejected combination FAILS SILENTLY otherwise,
8
+ * which is worse than not shipping the feature:
9
+ *
10
+ * - `renderMode: 'static'` — the prerender uses `renderToString`, which does
11
+ * not support Suspense: it emits an ERRORED boundary (`<!--$!-->`) plus a
12
+ * `<template data-msg="Switched to client rendering...">` and no warning.
13
+ * The artefact would ship a permanent client-rendered fallback.
14
+ * - `renderMode: 'isr'` — ISR caches a completed HTML STRING. Awaiting the
15
+ * deferred values to fill that string would make `defer()` a silent no-op
16
+ * that still reads like it streams; caching a stream is different
17
+ * machinery entirely.
18
+ * - `interactive: 'none'` — revealing a boundary needs React's inline
19
+ * `$RC`/`$RV` scripts, and this mode ships zero JS. The fallback would be
20
+ * permanent.
21
+ * - `interactive: 'islands'` — `mount()` returns after `hydrateIslandsOnPage()`
22
+ * and never hydrates the root, so nothing ever consumes the registry
23
+ * promises. Islands take their props from their own serialised payload, not
24
+ * from loader data, so there is no value in wiring them in.
25
+ */
26
+ export declare const assertDeferralSupported: (target: DeferralTarget) => void;
27
+
28
+ /** Render modes / interactive modes a page may combine with `defer()`. */
29
+ export declare interface DeferralTarget {
30
+ /** Route pattern or source file — named in the error so it is actionable. */
31
+ readonly page: string;
32
+ readonly renderMode: string;
33
+ readonly interactive: string;
34
+ }
35
+
36
+ /** Brand key on the object `defer()` returns. A single explicit container
37
+ * check — NOT a scan of arbitrary loader results for markers by shape. */
38
+ declare const DEFERRED_RESULT_TAG: "__voltroDeferredLoaderResult";
39
+
40
+ /** What a loader returns when it defers part of its data. Opaque to user
41
+ * code: build it with {@link defer}, read it with `useLoaderData()`. */
42
+ declare interface DeferredLoaderResult<TEager extends object = object, TDeferred extends Record<string, Promise<unknown>> = Record<string, Promise<unknown>>> {
43
+ readonly [DEFERRED_RESULT_TAG]: true;
44
+ /** Awaited before the shell renders — present in the SSR markup AND in the
45
+ * inlined `__voltro_state__` payload. */
46
+ readonly eager: TEager;
47
+ /** Streamed after the shell — each key becomes a promise on
48
+ * `useLoaderData()`, to be rendered through `<Await>`. */
49
+ readonly deferred: TDeferred;
50
+ }
51
+
52
+ /** Build the wire payload from what the server render was given. */
53
+ export declare const encodeRouterState: (input: RouterStateInput) => InlinedRouterState;
54
+
5
55
  declare interface ErrorBoundaryProps {
6
56
  readonly error: unknown;
7
57
  readonly reset: () => void;
8
58
  }
9
59
 
60
+ /**
61
+ * The wire shape. JSON cannot express `undefined`, which is why `ran` exists
62
+ * as a separate presence record: a loader that returned `undefined` is
63
+ * otherwise indistinguishable from one that never ran, and the client would
64
+ * re-run it on hydration (the flash this whole payload exists to remove).
65
+ */
66
+ export declare interface InlinedRouterState {
67
+ /** The page (leaf) loader's result — exactly the value the server render
68
+ * provided to the page's `LoaderDataContext`. */
69
+ readonly page?: unknown;
70
+ /** Per-layout loader results keyed by CHAIN INDEX (as a JSON string key).
71
+ * The index is the same one `descriptor.chain[i]` uses on both sides —
72
+ * see `wrapInChain` in ssr.tsx and in router.tsx. */
73
+ readonly segments?: Readonly<Record<string, unknown>>;
74
+ /** Which loaders actually RAN server-side. Drives cache seeding (i.e.
75
+ * "don't re-run this on hydration"); the values above drive the render. */
76
+ readonly ran?: {
77
+ readonly page?: boolean;
78
+ readonly segments?: ReadonlyArray<number>;
79
+ };
80
+ /** True when the server rendered the layout chain but left the page leaf as
81
+ * an EMPTY slot — the `renderMode:'spa'`-under-an-SSR-layout case. The
82
+ * server ran only the layout loaders; the page is client-only. The client's
83
+ * FIRST render must reproduce the identical empty slot so the layout chain
84
+ * hydrates without a mismatch, then mount the real page after commit. Absent
85
+ * (falsy) for every full-page SSR/ISR/static document. */
86
+ readonly pageClientOnly?: boolean;
87
+ /** Deferred (streamed) loader fields: field name -> client-registry id, for
88
+ * the page loader and per chain index. The VALUES are not here — they
89
+ * arrive later, published by the settle `<script>` each `<Await>` boundary
90
+ * emits as its promise resolves. Absent entirely when nothing deferred, so
91
+ * a non-deferring page's payload is byte-identical to what it always was. */
92
+ readonly deferred?: {
93
+ readonly page?: Readonly<Record<string, string>>;
94
+ readonly segments?: Readonly<Record<string, Readonly<Record<string, string>>>>;
95
+ };
96
+ }
97
+
10
98
  /**
11
99
  * How the client-side JS hydrates a server-rendered page.
12
100
  *
@@ -25,6 +113,9 @@ declare interface ErrorBoundaryProps {
25
113
  */
26
114
  declare type InteractiveMode = 'full' | 'islands' | 'none';
27
115
 
116
+ /** True for exactly the object {@link defer} returns. */
117
+ export declare const isDeferredLoaderResult: (value: unknown) => value is DeferredLoaderResult;
118
+
28
119
  declare interface LoaderContext {
29
120
  readonly params: Readonly<Record<string, string>>;
30
121
  readonly pathname: string;
@@ -139,6 +230,33 @@ declare interface PageMeta {
139
230
 
140
231
  export declare const parseCookieHeader: (raw: string | undefined) => Record<string, string>;
141
232
 
233
+ /**
234
+ * Split one loader result for a streaming render. `scope` is `'page'` for the
235
+ * page loader or the layout's chain index, and only feeds the registry id.
236
+ */
237
+ export declare const prepareDeferredLoaderData: (result: unknown, scope: "page" | number) => PreparedLoaderData;
238
+
239
+ /** One loader's result, prepared for a streaming SSR render. */
240
+ export declare interface PreparedLoaderData {
241
+ /** What the RENDERER gets: eager fields plus one tagged promise per
242
+ * deferred field. Identical to the plain result when nothing deferred. */
243
+ readonly renderData: unknown;
244
+ /** What the `__voltro_state__` payload gets: eager fields only. A promise
245
+ * serialises to `{}`, so the deferred half must never reach it. */
246
+ readonly stateData: unknown;
247
+ /** Deferred field name -> registry id, for the state payload. `undefined`
248
+ * when the loader did not defer — the wire shape is then byte-identical
249
+ * to what a non-deferring page has always emitted. */
250
+ readonly deferredIds: Readonly<Record<string, string>> | undefined;
251
+ }
252
+
253
+ /**
254
+ * The `<script>` every streaming SSR emitter puts in `<head>`, before the
255
+ * shell. Must precede any settle script — which is automatic, since settle
256
+ * scripts ride in the body.
257
+ */
258
+ export declare const renderDeferredRegistryScript: () => string;
259
+
142
260
  /**
143
261
  * Helper for the build pipeline: stringify the resolved meta into a
144
262
  * `<head>`-ready HTML fragment. Returns an empty string when no meta
@@ -208,6 +326,14 @@ export declare interface RenderPageOptions {
208
326
  * catalog at meta-resolve time. Optional: when absent, page
209
327
  * `meta(ctx)` callbacks see `ctx.locale = 'en'` as a safe default. */
210
328
  readonly locale?: string;
329
+ /** Render the LAYOUT CHAIN ONLY, with an empty page slot at the leaf
330
+ * ({@link PageSlot}) instead of the page Component — the
331
+ * `renderMode:'spa'`-under-an-SSR-layout case. The caller runs only the
332
+ * layout loaders (not the page loader) and pairs this with a state script
333
+ * carrying `pageClientOnly: true`, so the client reproduces the empty slot
334
+ * on its first render and mounts the page after hydration. When set, the
335
+ * descriptor need not carry a `Component`. */
336
+ readonly pageSlot?: boolean;
211
337
  }
212
338
 
213
339
  export declare interface RenderPageResult {
@@ -249,26 +375,75 @@ export declare interface RenderPageStreamResult {
249
375
  /**
250
376
  * Render a page to an HTML string using its layout chain + loader data. The
251
377
  * returned `html` is the body fragment — the caller composes the surrounding
252
- * document. Renders synchronously via `renderToString`; for the streaming
253
- * path (flush the shell before the whole tree is ready) use
378
+ * document. Renders synchronously via `renderToString`.
379
+ *
380
+ * This is the ARTEFACT renderer: static prerender and ISR, both of which need
381
+ * the whole document. See the doctrine note above before switching a caller to
254
382
  * {@link renderPageToStream}.
255
383
  */
256
384
  export declare const renderPageToHtml: (options: RenderPageOptions) => RenderPageResult;
257
385
 
258
386
  /**
259
- * Streaming SSR — the richer path alongside {@link renderPageToHtml}. Renders
260
- * the page via React's `renderToPipeableStream` so the server can flush the
261
- * shell (and each Suspense boundary as it resolves) instead of buffering the
262
- * whole document into one string. Same tree, same providers, same meta as the
263
- * sync path; the difference is time-to-first-byte and native Suspense
264
- * streaming.
387
+ * Streaming SSR — the RESPONSE renderer, for `renderMode: 'ssr'`. Renders via
388
+ * React's `renderToPipeableStream` so the server can flush the shell (and each
389
+ * Suspense boundary as it resolves) instead of buffering the whole document
390
+ * into one string. Same tree, same providers, same meta as the sync path.
265
391
  *
266
392
  * The caller drives the response: write the doctype + `<head>` (using the
267
393
  * synchronously-returned `meta`) + opening `<body><div id="root">` in
268
- * `onShellReady`, `stream.pipe(res)`, then close the document in `onAllReady`.
394
+ * `onShellReady`, pipe the stream, then close the document once it ends.
395
+ * Both boot paths do that through ONE shared helper —
396
+ * `streamSsrResponse` in `@voltro/cli`'s `ssrShell.ts` — rather than
397
+ * hand-mirroring the sequence, which is the drift class that previously cost
398
+ * this pipeline its layout-loader data in dev.
399
+ *
400
+ * Two details the caller MUST get right, both learned the hard way:
401
+ *
402
+ * - **Pass `bootstrapModules`, don't leave the entry `<script type="module">`
403
+ * in the shell.** A plain module script is deferred until the document is
404
+ * fully parsed — on a streamed response that is AFTER the last deferred
405
+ * boundary, which deletes the entire benefit. React emits `bootstrapModules`
406
+ * as `async` at the end of the shell instead.
407
+ * - **Pipe through a `PassThrough`.** React's `pipe()` calls `end()` on the
408
+ * destination itself, so writing the closing `</div>` + shell tail straight
409
+ * to the response would land after it was already closed.
269
410
  */
270
411
  export declare const renderPageToStream: (options: RenderPageStreamOptions) => RenderPageStreamResult;
271
412
 
413
+ /**
414
+ * The full `<script>` tag every SSR emitter injects into `<head>`. Emitters
415
+ * call THIS, never the pieces — that is what keeps the dev / serve / prerender
416
+ * paths on one payload shape.
417
+ */
418
+ export declare const renderRouterStateScript: (input: RouterStateInput) => string;
419
+
420
+ /** id of the `<script type="application/json">` tag carrying the payload. */
421
+ export declare const ROUTER_STATE_SCRIPT_ID = "__voltro_state__";
422
+
423
+ export declare interface RouterStateInput {
424
+ /** Page loader result passed to `renderPageToHtml` / `renderPageToStream`. */
425
+ readonly loaderData: unknown;
426
+ /** Layout loader results, keyed by chain index — the same object passed to
427
+ * the renderer, so the inlined payload cannot disagree with the markup. */
428
+ readonly segmentLoaderData?: Readonly<Record<number, unknown>> | undefined;
429
+ /** Whether the page module actually exported a `loader`. Not derivable from
430
+ * `loaderData` (a loader may legitimately resolve to `undefined`/`null`,
431
+ * and the SSR paths default the field to `null` when there is no loader). */
432
+ readonly pageLoaderRan: boolean;
433
+ /** Set by the SSR-layout-shell path (a `renderMode:'spa'` page under an SSR
434
+ * layout chain): the server rendered the layouts + an empty page slot and
435
+ * ran only the layout loaders. Tells the client to reproduce the empty slot
436
+ * on its first render, then mount the page after hydration. Omit for every
437
+ * full-page render. */
438
+ readonly pageClientOnly?: boolean | undefined;
439
+ /** Page-loader deferred fields: name -> registry id. Produced by
440
+ * `prepareDeferredLoaderData` in the SSR pipeline; omit when nothing
441
+ * deferred (which is every non-streaming emitter). */
442
+ readonly deferredPageIds?: Readonly<Record<string, string>> | undefined;
443
+ /** Layout-loader deferred fields, keyed by chain index. */
444
+ readonly deferredSegmentIds?: Readonly<Record<number, Readonly<Record<string, string>>>> | undefined;
445
+ }
446
+
272
447
  /**
273
448
  * One layer in a page's nesting chain. Each represents a directory level
274
449
  * in the file-convention layout (or a route group).
@@ -301,11 +476,11 @@ declare interface RouteSegment {
301
476
  }
302
477
 
303
478
  /**
304
- * Serialise loader data into a `<script>` payload safe to embed in
305
- * HTML. The result is wrapped in `<script type="application/json"
306
- * id="__voltro_state__">` by the caller; here we just produce the
307
- * JSON body with `</` sequences escaped (the well-known SSR XSS
308
- * vector).
479
+ * Serialise arbitrary data for inlining into an HTML `<script>` body.
480
+ *
481
+ * Escaping `<` is the well-known SSR XSS fix: it makes a `</script>` inside a
482
+ * string value un-parseable as a closing tag while staying valid JSON (`\u003c`
483
+ * decodes back to `<`), so no consumer has to un-escape anything.
309
484
  */
310
485
  export declare const serialiseStateForInlining: (state: unknown) => string;
311
486
 
package/dist/ssr.js CHANGED
@@ -1,47 +1,48 @@
1
- import { c as e, d as t, r as n, t as r, x as i } from "./serverContext-DSValgbm.js";
2
- import { createElement as a } from "react";
3
- import { renderToPipeableStream as o, renderToString as s } from "react-dom/server";
1
+ import { _ as e, a as t, g as n, m as r, n as i, p as a, s as o, t as s } from "./routerState-DpUqogK8.js";
2
+ import { C as c, c as l, p as u, r as d, t as f, u as p } from "./serverContext-COzippNt.js";
3
+ import { createElement as m } from "react";
4
+ import { renderToPipeableStream as h, renderToString as g } from "react-dom/server";
4
5
  //#region src/ssr.tsx
5
- var c = (t, n, r, i) => {
6
- let o = a(e.Provider, { value: r }, a(t));
7
- if (!n) return o;
8
- for (let t = n.length - 1; t >= 0; t--) {
9
- let r = n[t];
10
- if (r.Layout) {
11
- let n = r.Layout;
12
- o = a(e.Provider, { value: i(t) }, a(n, { children: o }));
6
+ var _ = (e, t, n, r) => {
7
+ let i = m(l.Provider, { value: n }, m(e));
8
+ if (!t) return i;
9
+ for (let e = t.length - 1; e >= 0; e--) {
10
+ let n = t[e];
11
+ if (n.Layout) {
12
+ let t = n.Layout;
13
+ i = m(l.Provider, { value: r(e) }, m(t, { children: i }));
13
14
  }
14
15
  }
15
- return o;
16
- }, l = (e) => {
17
- let { descriptor: n, params: o, pathname: s, loaderData: l, segmentLoaderData: u, requestContext: d, outerWrap: f, locale: p } = e, m = i(n.meta, o, l, p), h = {
18
- pathname: s,
16
+ return i;
17
+ }, v = (e) => {
18
+ let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: l, pageSlot: d } = e, h = c(t.meta, n, i, l), g = {
19
+ pathname: r,
19
20
  search: "",
20
- params: o,
21
- loaderData: l,
21
+ params: n,
22
+ loaderData: i,
22
23
  navigate: () => {
23
24
  throw Error("navigate() is not supported during server-side rendering. Trigger navigations on the client only.");
24
25
  },
25
26
  prefetch: () => {},
26
27
  registerBlocker: () => () => {},
27
28
  blocked: null
28
- }, g = n.Component;
29
- if (!g) throw Error(`SSR received a descriptor with no Component for "${s}". Lazy page routes are a client-only optimization and must not be used on the server.`);
30
- let _ = a(t.Provider, { value: h }, c(g, n.chain, l, (e) => u?.[e]));
31
- return d && (_ = a(r.Provider, { value: d }, _)), f && (_ = f(_)), {
32
- tree: _,
33
- meta: m
29
+ }, v = d ? p : t.Component;
30
+ if (!v) throw Error(`SSR received a descriptor with no Component for "${r}". Lazy page routes are a client-only optimization and must not be used on the server.`);
31
+ let y = m(u.Provider, { value: g }, _(v, t.chain, d ? void 0 : i, (e) => a?.[e]));
32
+ return o && (y = m(f.Provider, { value: o }, y)), s && (y = s(y)), {
33
+ tree: y,
34
+ meta: h
34
35
  };
35
- }, u = (e) => {
36
- let { tree: t, meta: n } = l(e);
36
+ }, y = (e) => {
37
+ let { tree: t, meta: n } = v(e);
37
38
  return {
38
- html: s(t),
39
+ html: g(t),
39
40
  meta: n
40
41
  };
41
- }, d = (e) => {
42
- let { tree: t, meta: n } = l(e);
42
+ }, b = (e) => {
43
+ let { tree: t, meta: n } = v(e);
43
44
  return {
44
- stream: o(t, {
45
+ stream: h(t, {
45
46
  ...e.bootstrapModules ? { bootstrapModules: [...e.bootstrapModules] } : {},
46
47
  ...e.onShellReady ? { onShellReady: e.onShellReady } : {},
47
48
  ...e.onShellError ? { onShellError: e.onShellError } : {},
@@ -49,20 +50,20 @@ var c = (t, n, r, i) => {
49
50
  }),
50
51
  meta: n
51
52
  };
52
- }, f = (e) => {
53
+ }, x = (e) => {
53
54
  if (!e) return "";
54
55
  let t = [];
55
- if (typeof e.title == "string" && t.push(`<title>${m(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${h(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${h(e.canonical)}" />`), e.tags) for (let n of e.tags) {
56
- let e = n.name ? `name="${h(n.name)}"` : n.property ? `property="${h(n.property)}"` : "";
57
- e && t.push(`<meta ${e} content="${h(n.content)}" />`);
56
+ if (typeof e.title == "string" && t.push(`<title>${S(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${C(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${C(e.canonical)}" />`), e.tags) for (let n of e.tags) {
57
+ let e = n.name ? `name="${C(n.name)}"` : n.property ? `property="${C(n.property)}"` : "";
58
+ e && t.push(`<meta ${e} content="${C(n.content)}" />`);
58
59
  }
59
60
  if (e.links) for (let n of e.links) {
60
61
  let e = [
61
- `rel="${h(n.rel)}"`,
62
- `href="${h(n.href)}"`,
63
- n.hreflang ? `hreflang="${h(n.hreflang)}"` : "",
64
- n.type ? `type="${h(n.type)}"` : "",
65
- n.title ? `title="${h(n.title)}"` : ""
62
+ `rel="${C(n.rel)}"`,
63
+ `href="${C(n.href)}"`,
64
+ n.hreflang ? `hreflang="${C(n.hreflang)}"` : "",
65
+ n.type ? `type="${C(n.type)}"` : "",
66
+ n.title ? `title="${C(n.title)}"` : ""
66
67
  ].filter(Boolean).join(" ");
67
68
  t.push(`<link ${e} />`);
68
69
  }
@@ -71,6 +72,6 @@ var c = (t, n, r, i) => {
71
72
  t.push(`<script type="application/ld+json" data-voltro-page-jsonld>${e}<\/script>`);
72
73
  }
73
74
  return t.join("\n ");
74
- }, p = (e) => JSON.stringify(e ?? null).replace(/</g, "\\u003c"), m = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), h = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
75
+ }, S = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), C = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
75
76
  //#endregion
76
- export { n as parseCookieHeader, f as renderMetaToHtml, u as renderPageToHtml, d as renderPageToStream, p as serialiseStateForInlining };
77
+ export { s as ROUTER_STATE_SCRIPT_ID, o as assertDeferralSupported, i as encodeRouterState, a as isDeferredLoaderResult, d as parseCookieHeader, r as prepareDeferredLoaderData, n as renderDeferredRegistryScript, x as renderMetaToHtml, y as renderPageToHtml, b as renderPageToStream, t as renderRouterStateScript, e as serialiseStateForInlining };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/web",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "The Voltro web framework — file-based routing, render modes (SSR / SSG / islands), the page-export contract, data hooks, and the browser mount.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -52,8 +52,8 @@
52
52
  "node": ">=24.0.0"
53
53
  },
54
54
  "dependencies": {
55
- "@voltro/client": "0.7.0",
56
- "@voltro/ui": "0.7.0"
55
+ "@voltro/client": "0.9.0",
56
+ "@voltro/ui": "0.9.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@effect/platform": "^0.96.2",
@@ -1,74 +0,0 @@
1
- import { t as e } from "./frameworkBoot-C_d7E8Fj.js";
2
- import { StrictMode as t, createElement as n } from "react";
3
- import { createRoot as r, hydrateRoot as i } from "react-dom/client";
4
- import { jsx as a } from "react/jsx-runtime";
5
- //#region src/islands.tsx
6
- var o = /* @__PURE__ */ new Map(), s = (e, t) => {
7
- o.set(t.name, e);
8
- let r = t.hydrate ?? "visible", i = (i) => n("div", {
9
- "data-voltro-island": "",
10
- "data-island-name": t.name,
11
- "data-island-hydrate": r,
12
- "data-island-props": l(i)
13
- }, n(e, i));
14
- return i.displayName = `Island(${t.name})`, i;
15
- }, c = (e) => o.get(e), l = (e) => JSON.stringify(e ?? {}).replace(/</g, "\\u003c"), u = (e) => {
16
- if (!e) return {};
17
- try {
18
- return JSON.parse(e);
19
- } catch {
20
- return {};
21
- }
22
- }, d = () => {
23
- let e = document.querySelectorAll("[data-voltro-island]:not([data-voltro-hydrated])");
24
- for (let t of e) {
25
- let e = t.dataset.islandName ?? "", r = o.get(e);
26
- if (!r) {
27
- console.warn(`[voltro] island "${e}" referenced in DOM but not registered. Did the island file get imported in this bundle?`);
28
- continue;
29
- }
30
- let a = u(t.dataset.islandProps ?? null), s = t.dataset.islandHydrate ?? "visible";
31
- t.setAttribute("data-voltro-hydrated", "pending");
32
- let c = () => {
33
- t.getAttribute("data-voltro-hydrated") !== "done" && (t.setAttribute("data-voltro-hydrated", "done"), i(t, n(r, a)));
34
- };
35
- if (s === "load") c();
36
- else if (s === "idle") {
37
- let e = globalThis.requestIdleCallback;
38
- e ? e(c) : setTimeout(c, 0);
39
- } else if (s === "visible") {
40
- let e = new IntersectionObserver((t) => {
41
- t.some((e) => e.isIntersecting) && (e.disconnect(), c());
42
- }, { rootMargin: "64px" });
43
- e.observe(t);
44
- } else if (s === "interaction") {
45
- let e = () => {
46
- t.removeEventListener("pointerdown", e), t.removeEventListener("keydown", e), c();
47
- };
48
- t.addEventListener("pointerdown", e, { passive: !0 }), t.addEventListener("keydown", e);
49
- }
50
- }
51
- }, f = (e) => `${window.location.protocol === "https:" ? "wss:" : "ws:"}//${window.location.host}/ws/${e}`, p = (n, o) => {
52
- let s = document.getElementById(o.rootId ?? "root");
53
- if (!s) throw Error(`@voltro/web: no #${o.rootId ?? "root"} element in the document`);
54
- let c = Object.entries(o.apis).map(([e, t]) => ({
55
- name: e,
56
- group: t.group,
57
- descriptors: t.descriptors ?? {},
58
- wsUrl: t.wsUrl ?? f(e),
59
- headers: t.headers
60
- })), l = document.getElementById("__voltro_state__") !== null && s.children.length > 0, u = document.querySelector("meta[name=\"voltro-interactive\"]")?.getAttribute("content") ?? "full";
61
- if (u === "none") return;
62
- if (u === "islands") {
63
- d();
64
- return;
65
- }
66
- let p = /* @__PURE__ */ a(t, { children: /* @__PURE__ */ a(e, {
67
- App: n,
68
- apis: c,
69
- ...o.Devtools ? { Devtools: o.Devtools } : {}
70
- }) });
71
- l ? i(s, p) : r(s).render(p);
72
- };
73
- //#endregion
74
- export { s as i, c as n, d as r, p as t };