@nifrajs/web-react 0.1.0-alpha.1

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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/await.d.ts +29 -0
  4. package/dist/await.d.ts.map +1 -0
  5. package/dist/await.js +50 -0
  6. package/dist/await.js.map +1 -0
  7. package/dist/client.d.ts +12 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +39 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/compose.d.ts +9 -0
  12. package/dist/compose.d.ts.map +1 -0
  13. package/dist/compose.js +16 -0
  14. package/dist/compose.js.map +1 -0
  15. package/dist/content.d.ts +18 -0
  16. package/dist/content.d.ts.map +1 -0
  17. package/dist/content.js +14 -0
  18. package/dist/content.js.map +1 -0
  19. package/dist/error.d.ts +8 -0
  20. package/dist/error.d.ts.map +1 -0
  21. package/dist/error.js +32 -0
  22. package/dist/error.js.map +1 -0
  23. package/dist/fetcher.d.ts +23 -0
  24. package/dist/fetcher.d.ts.map +1 -0
  25. package/dist/fetcher.js +45 -0
  26. package/dist/fetcher.js.map +1 -0
  27. package/dist/i18n.d.ts +20 -0
  28. package/dist/i18n.d.ts.map +1 -0
  29. package/dist/i18n.js +25 -0
  30. package/dist/i18n.js.map +1 -0
  31. package/dist/image.d.ts +25 -0
  32. package/dist/image.d.ts.map +1 -0
  33. package/dist/image.js +21 -0
  34. package/dist/image.js.map +1 -0
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +29 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/island.d.ts +27 -0
  40. package/dist/island.d.ts.map +1 -0
  41. package/dist/island.js +23 -0
  42. package/dist/island.js.map +1 -0
  43. package/dist/query.d.ts +17 -0
  44. package/dist/query.d.ts.map +1 -0
  45. package/dist/query.js +58 -0
  46. package/dist/query.js.map +1 -0
  47. package/package.json +101 -0
  48. package/src/await.ts +75 -0
  49. package/src/client.ts +42 -0
  50. package/src/compose.ts +17 -0
  51. package/src/content.ts +27 -0
  52. package/src/error.ts +33 -0
  53. package/src/fetcher.ts +66 -0
  54. package/src/i18n.ts +36 -0
  55. package/src/image.ts +43 -0
  56. package/src/index.ts +30 -0
  57. package/src/island.ts +39 -0
  58. package/src/query.ts +75 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nifra Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @nifrajs/web-react
2
+
3
+ React render adapter for @nifrajs/web — SSR + hydration (Bun-native JSX, no Babel).
4
+
5
+ Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework — one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add @nifrajs/web-react
11
+ ```
12
+
13
+ ## Docs
14
+
15
+ - Reference: <https://nifra.dev/docs>
16
+ - AI-readable: <https://nifra.dev/llms.txt>
17
+
18
+ MIT
@@ -0,0 +1,29 @@
1
+ import type { Deferred } from "@nifrajs/web";
2
+ /**
3
+ * `@nifrajs/web-react/await` — the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
4
+ * and imports only `react` + a type from `@nifrajs/web` (never `react-dom/server`), so route components
5
+ * can use it without dragging server code into the client bundle. No JSX (the package builds with
6
+ * plain `tsc`).
7
+ *
8
+ * The server streams the boundary: `use(promise)` suspends, React flushes the fallback then the
9
+ * resolved content mid-stream. On the client the registry promise is already settled (the core's
10
+ * streamed `__nifraResolve` script tagged it `fulfilled`), so `use()` returns the value synchronously
11
+ * — React hydrates the server-revealed boundary's content directly (no re-fetch, no fallback flash).
12
+ */
13
+ import { type ReactNode } from "react";
14
+ export interface AwaitProps<T> {
15
+ /** A `Deferred<T>` from a loader's `defer(...)`, or an already-resolved value (client nav). */
16
+ readonly resolve: Deferred<T> | T;
17
+ readonly fallback?: ReactNode;
18
+ /** Rendered if the deferred rejects (the streamed `__nifraReject`). Without it the error
19
+ * propagates to the nearest error boundary. */
20
+ readonly errorFallback?: (error: unknown) => ReactNode;
21
+ readonly children: (value: T) => ReactNode;
22
+ }
23
+ /**
24
+ * Render deferred loader data: show `fallback` until the `Deferred` settles (streamed in by the
25
+ * server), then `children(value)`. An already-resolved `resolve` (a client navigation awaited it)
26
+ * renders immediately. Pairs with a loader's `defer(...)`.
27
+ */
28
+ export declare function Await<T>(props: AwaitProps<T>): ReactNode;
29
+ //# sourceMappingURL=await.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"await.d.ts","sourceRoot":"","sources":["../src/await.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C;;;;;;;;;;GAUG;AACH,OAAO,EAA4B,KAAK,SAAS,EAAiB,MAAM,OAAO,CAAA;AAE/E,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,+FAA+F;IAC/F,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IACjC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;IAC7B;mDAC+C;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,CAAA;IACtD,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,CAAA;CAC3C;AAiCD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,CAcxD"}
package/dist/await.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * `@nifrajs/web-react/await` — the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
3
+ * and imports only `react` + a type from `@nifrajs/web` (never `react-dom/server`), so route components
4
+ * can use it without dragging server code into the client bundle. No JSX (the package builds with
5
+ * plain `tsc`).
6
+ *
7
+ * The server streams the boundary: `use(promise)` suspends, React flushes the fallback then the
8
+ * resolved content mid-stream. On the client the registry promise is already settled (the core's
9
+ * streamed `__nifraResolve` script tagged it `fulfilled`), so `use()` returns the value synchronously
10
+ * — React hydrates the server-revealed boundary's content directly (no re-fetch, no fallback flash).
11
+ */
12
+ import { Component, createElement, Suspense, use } from "react";
13
+ // Catches the rejection `use()` throws when the deferred rejects → renders `errorFallback`. A class
14
+ // is required (React error boundaries have no hook form). Local so `<Await>` is self-contained.
15
+ class AwaitBoundary extends Component {
16
+ state = { caught: false, error: undefined };
17
+ static getDerivedStateFromError(error) {
18
+ return { caught: true, error };
19
+ }
20
+ render() {
21
+ return this.state.caught ? this.props.fallback(this.state.error) : this.props.children;
22
+ }
23
+ }
24
+ function isDeferred(value) {
25
+ return (typeof value === "object" &&
26
+ value !== null &&
27
+ value.__nifra_deferred === true);
28
+ }
29
+ // Type-erased (non-generic) so `createElement` infers cleanly; `Await<T>` casts at the call site.
30
+ function Resolved(props) {
31
+ return props.render(use(props.marker.promise));
32
+ }
33
+ /**
34
+ * Render deferred loader data: show `fallback` until the `Deferred` settles (streamed in by the
35
+ * server), then `children(value)`. An already-resolved `resolve` (a client navigation awaited it)
36
+ * renders immediately. Pairs with a loader's `defer(...)`.
37
+ */
38
+ export function Await(props) {
39
+ if (!isDeferred(props.resolve))
40
+ return props.children(props.resolve);
41
+ const suspense = createElement(Suspense, { fallback: props.fallback }, createElement(Resolved, {
42
+ marker: props.resolve,
43
+ // safe: the registry promise resolves to T; Resolved is type-erased to satisfy createElement.
44
+ render: props.children,
45
+ }));
46
+ return props.errorFallback
47
+ ? createElement(AwaitBoundary, { fallback: props.errorFallback }, suspense)
48
+ : suspense;
49
+ }
50
+ //# sourceMappingURL=await.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"await.js","sourceRoot":"","sources":["../src/await.ts"],"names":[],"mappings":"AACA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAkB,QAAQ,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAY/E,oGAAoG;AACpG,gGAAgG;AAChG,MAAM,aAAc,SAAQ,SAG3B;IACU,KAAK,GAAwC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IACzF,MAAM,CAAC,wBAAwB,CAAC,KAAc;QAC5C,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IAChC,CAAC;IACQ,MAAM;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;IACxF,CAAC;CACF;AAED,SAAS,UAAU,CAAI,KAAsB;IAC3C,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,KAAK,IAAI;QACb,KAAwC,CAAC,gBAAgB,KAAK,IAAI,CACpE,CAAA;AACH,CAAC;AAED,kGAAkG;AAClG,SAAS,QAAQ,CAAC,KAGjB;IACC,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;AAChD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,KAAK,CAAI,KAAoB;IAC3C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACpE,MAAM,QAAQ,GAAG,aAAa,CAC5B,QAAQ,EACR,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,EAC5B,aAAa,CAAC,QAAQ,EAAE;QACtB,MAAM,EAAE,KAAK,CAAC,OAAO;QACrB,8FAA8F;QAC9F,MAAM,EAAE,KAAK,CAAC,QAAyC;KACxD,CAAC,CACH,CAAA;IACD,OAAO,KAAK,CAAC,aAAa;QACxB,CAAC,CAAC,aAAa,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,aAAa,EAAE,EAAE,QAAQ,CAAC;QAC3E,CAAC,CAAC,QAAQ,CAAA;AACd,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { MountRouterOptions, RenderProps } from "@nifrajs/web";
2
+ export { errorBoundary } from "./error.js";
3
+ /** Hydrate a server-rendered React layout `chain` (with the loader `props`) inside `container`. */
4
+ export declare function hydrate(chain: readonly unknown[], props: RenderProps, container: unknown): void;
5
+ /**
6
+ * Hydrate a stateful React Router. `useSyncExternalStore` subscribes to the agnostic store and
7
+ * re-renders the matched layout chain on each store change — so client navigations swap routes
8
+ * without a full reload. `getServerSnapshot` (3rd arg) returns the initial state, matching the
9
+ * SSR markup on hydration.
10
+ */
11
+ export declare function mountRouter(options: MountRouterOptions): void;
12
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAcnE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,mGAAmG;AACnG,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,GAAG,IAAI,CAE/F;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAc7D"}
package/dist/client.js ADDED
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @nifrajs/web-react/client — React client runtime. `hydrate` hydrates a single SSR'd route;
3
+ * `mountRouter` hydrates a stateful Router that subscribes to the agnostic store (via
4
+ * `useSyncExternalStore`) and re-renders the matched chain on every client navigation (no full
5
+ * reload). Kept in its own entry so server code stays out of the client bundle.
6
+ */
7
+ import { createElement, useSyncExternalStore } from "react";
8
+ import { hydrateRoot } from "react-dom/client";
9
+ import { compose } from "./compose.js";
10
+ import { setMountedRouter } from "./fetcher.js";
11
+ // The `_error` boundary chain element — defined in its own (react-dom-free) module, re-exported here so
12
+ // nifra's client codegen resolves it from `@nifrajs/web-react/client` alongside `mountRouter`.
13
+ export { errorBoundary } from "./error.js";
14
+ /** Hydrate a server-rendered React layout `chain` (with the loader `props`) inside `container`. */
15
+ export function hydrate(chain, props, container) {
16
+ hydrateRoot(container, compose(chain, props));
17
+ }
18
+ /**
19
+ * Hydrate a stateful React Router. `useSyncExternalStore` subscribes to the agnostic store and
20
+ * re-renders the matched layout chain on each store change — so client navigations swap routes
21
+ * without a full reload. `getServerSnapshot` (3rd arg) returns the initial state, matching the
22
+ * SSR markup on hydration.
23
+ */
24
+ export function mountRouter(options) {
25
+ const { router, routes, container } = options;
26
+ setMountedRouter(router); // expose it to useFetcher/useFetchers (same page, client-only)
27
+ const Router = () => {
28
+ const state = useSyncExternalStore(router.subscribe, router.snapshot, router.snapshot);
29
+ return compose(routes[state.routeId] ?? [], {
30
+ data: state.data,
31
+ actionData: state.actionData,
32
+ pending: state.pending,
33
+ // The in-flight submission (for optimistic UI) — spread only when present.
34
+ ...(state.submission ? { submission: state.submission } : {}),
35
+ });
36
+ };
37
+ hydrateRoot(container, createElement(Router));
38
+ }
39
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AACH,OAAO,EAAE,aAAa,EAA0B,oBAAoB,EAAE,MAAM,OAAO,CAAA;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,wGAAwG;AACxG,+FAA+F;AAC/F,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,mGAAmG;AACnG,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB,EAAE,SAAkB;IACvF,WAAW,CAAC,SAAoB,EAAE,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IAC7C,gBAAgB,CAAC,MAAM,CAAC,CAAA,CAAC,+DAA+D;IACxF,MAAM,MAAM,GAAsB,GAAG,EAAE;QACrC,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;QACtF,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE;YAC1C,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,2EAA2E;YAC3E,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC,CAAA;IACJ,CAAC,CAAA;IACD,WAAW,CAAC,SAAoB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,CAAA;AAC1D,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { RenderProps } from "@nifrajs/web";
2
+ import { type ReactNode } from "react";
3
+ /**
4
+ * Fold a layout chain (outermost layout → page) into a single React tree: the page
5
+ * (innermost) receives `props` (the loader data); each layout wraps the child via its
6
+ * `children`. Shared by the server adapter (renderToString) and the client (hydrateRoot).
7
+ */
8
+ export declare function compose(chain: readonly unknown[], props: RenderProps): ReactNode;
9
+ //# sourceMappingURL=compose.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAyC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE7E;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,SAAS,CAQhF"}
@@ -0,0 +1,16 @@
1
+ import { createElement } from "react";
2
+ /**
3
+ * Fold a layout chain (outermost layout → page) into a single React tree: the page
4
+ * (innermost) receives `props` (the loader data); each layout wraps the child via its
5
+ * `children`. Shared by the server adapter (renderToString) and the client (hydrateRoot).
6
+ */
7
+ export function compose(chain, props) {
8
+ const last = chain.length - 1;
9
+ let node = createElement(chain[last], props);
10
+ for (let i = last - 1; i >= 0; i--) {
11
+ // children passed as the 3rd arg (not a `children` prop) — React's canonical form.
12
+ node = createElement(chain[i], null, node);
13
+ }
14
+ return node;
15
+ }
16
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAA0C,MAAM,OAAO,CAAA;AAE7E;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,IAAI,IAAI,GAAc,aAAa,CAAC,KAAK,CAAC,IAAI,CAAmC,EAAE,KAAK,CAAC,CAAA;IACzF,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,mFAAmF;QACnF,IAAI,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAsB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IACjE,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@nifrajs/web-react/content` — render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
3
+ * `.html`, or any Markdown→HTML string) into the DOM via `dangerouslySetInnerHTML`. Pairs with content
4
+ * collections: `<Content html={entry.html} />`.
5
+ *
6
+ * Security: this injects raw HTML, so the `html` MUST be trusted (your own Markdown/content, rendered
7
+ * at build/server time) — never pass user-supplied HTML without sanitizing it first.
8
+ */
9
+ import { type ComponentPropsWithoutRef, type ElementType, type ReactElement } from "react";
10
+ export interface ContentProps extends Omit<ComponentPropsWithoutRef<"div">, "dangerouslySetInnerHTML" | "children"> {
11
+ /** Trusted, pre-rendered HTML (e.g. `entry.html`). */
12
+ readonly html: string;
13
+ /** Wrapper element (default `"div"`). */
14
+ readonly as?: ElementType;
15
+ }
16
+ /** Render trusted HTML into a wrapper element. Extra props (`className`, `id`, `style`, …) pass through. */
17
+ export declare function Content({ html, as, ...rest }: ContentProps): ReactElement;
18
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,KAAK,wBAAwB,EAE7B,KAAK,WAAW,EAChB,KAAK,YAAY,EAClB,MAAM,OAAO,CAAA;AAEd,MAAM,WAAW,YACf,SAAQ,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,yBAAyB,GAAG,UAAU,CAAC;IACrF,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,CAAC,EAAE,WAAW,CAAA;CAC1B;AAED,4GAA4G;AAC5G,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,YAAY,GAAG,YAAY,CAEzE"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@nifrajs/web-react/content` — render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
3
+ * `.html`, or any Markdown→HTML string) into the DOM via `dangerouslySetInnerHTML`. Pairs with content
4
+ * collections: `<Content html={entry.html} />`.
5
+ *
6
+ * Security: this injects raw HTML, so the `html` MUST be trusted (your own Markdown/content, rendered
7
+ * at build/server time) — never pass user-supplied HTML without sanitizing it first.
8
+ */
9
+ import { createElement, } from "react";
10
+ /** Render trusted HTML into a wrapper element. Extra props (`className`, `id`, `style`, …) pass through. */
11
+ export function Content({ html, as, ...rest }) {
12
+ return createElement(as ?? "div", { ...rest, dangerouslySetInnerHTML: { __html: html } });
13
+ }
14
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAEL,aAAa,GAGd,MAAM,OAAO,CAAA;AAUd,4GAA4G;AAC5G,MAAM,UAAU,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAgB;IACzD,OAAO,aAAa,CAAC,EAAE,IAAI,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;AAC3F,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Build an error-boundary chain element bound to `fallback` (a route's `_error` component). nifra's
3
+ * client codegen inserts it before the page in the matched chain; a render error in the subtree renders
4
+ * `fallback` with `{ data: { name, message } }` instead of crashing the app. DOM-transparent (it renders
5
+ * its children directly — no wrapper element), so it adds no markup and never disturbs hydration.
6
+ */
7
+ export declare function errorBoundary(fallback: unknown): unknown;
8
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAOA;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAmBxD"}
package/dist/error.js ADDED
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `errorBoundary` — the React error-boundary chain element for nifra's `_error.tsx`. Kept in its own
3
+ * module (imports only `react`, never `react-dom/client`) so it's unit-testable off the DOM and the
4
+ * client codegen can import it from `@nifrajs/web-react/client` (which re-exports it).
5
+ */
6
+ import { Component, createElement } from "react";
7
+ /**
8
+ * Build an error-boundary chain element bound to `fallback` (a route's `_error` component). nifra's
9
+ * client codegen inserts it before the page in the matched chain; a render error in the subtree renders
10
+ * `fallback` with `{ data: { name, message } }` instead of crashing the app. DOM-transparent (it renders
11
+ * its children directly — no wrapper element), so it adds no markup and never disturbs hydration.
12
+ */
13
+ export function errorBoundary(fallback) {
14
+ return class NifraErrorBoundary extends Component {
15
+ constructor(props) {
16
+ super(props);
17
+ this.state = { error: null };
18
+ }
19
+ static getDerivedStateFromError(error) {
20
+ return { error };
21
+ }
22
+ render() {
23
+ const { error } = this.state;
24
+ if (error === null)
25
+ return this.props.children;
26
+ // `fallback` is the route's `_error` default export — opaque to the core, a component here.
27
+ const Fallback = fallback;
28
+ return createElement(Fallback, { data: { name: error.name, message: error.message } });
29
+ }
30
+ };
31
+ }
32
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAA0C,MAAM,OAAO,CAAA;AAExF;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAiB;IAG7C,OAAO,MAAM,kBAAmB,SAAQ,SAAuB;QAC7D,YAAY,KAAY;YACtB,KAAK,CAAC,KAAK,CAAC,CAAA;YACZ,IAAI,CAAC,KAAK,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAC9B,CAAC;QACD,MAAM,CAAC,wBAAwB,CAAC,KAAY;YAC1C,OAAO,EAAE,KAAK,EAAE,CAAA;QAClB,CAAC;QACQ,MAAM;YACb,MAAM,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;YAC5B,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAA;YAC9C,4FAA4F;YAC5F,MAAM,QAAQ,GAAG,QAA0E,CAAA;YAC3F,OAAO,aAAa,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;QACxF,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { ClientRouter, Fetcher, FetcherState } from "@nifrajs/web";
2
+ /** Register (or clear) the router that owns fetchers — called by `mountRouter`. Not for app use. */
3
+ export declare function setMountedRouter(router: ClientRouter | undefined): void;
4
+ /** A fetcher's reactive {@link FetcherState} plus its imperative `load`/`submit`. */
5
+ export interface FetcherHandle extends FetcherState {
6
+ /** Load a route path's loader data into this fetcher (concurrent; doesn't touch the active view). */
7
+ readonly load: (path: string) => Promise<void>;
8
+ /** Submit an action into this fetcher (concurrent); honors `X-Nifra-Revalidate`. */
9
+ readonly submit: (action: string, body: NonNullable<RequestInit["body"]>) => Promise<void>;
10
+ }
11
+ /**
12
+ * Subscribe to the independent fetcher for `key` (created lazily, stable across renders). Returns its
13
+ * state (`pending`/`data`/`actionData`/`submission`) + `load`/`submit`. Multiple `useFetcher` calls
14
+ * with different keys run concurrently without disturbing the active route or each other.
15
+ */
16
+ export declare function useFetcher(key: string): FetcherHandle;
17
+ /**
18
+ * Subscribe to the whole live fetcher collection — for a global busy view (e.g. "3 saving…"). Read
19
+ * each entry's `.snapshot()` for its state. Re-renders whenever any fetcher transitions or a new one
20
+ * is created.
21
+ */
22
+ export declare function useFetchers(): readonly Fetcher[];
23
+ //# sourceMappingURL=fetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAmBvE,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CAEvE;AASD,qFAAqF;AACrF,MAAM,WAAW,aAAc,SAAQ,YAAY;IACjD,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9C,oFAAoF;IACpF,QAAQ,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3F;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,CAQrD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,SAAS,OAAO,EAAE,CAMhD"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * `@nifrajs/web-react/fetcher` — React bindings for concurrent fetchers. `useFetcher(key)` subscribes a
3
+ * component to an independent {@link Fetcher} (via `useSyncExternalStore`) and returns its reactive
4
+ * state plus `load`/`submit`; `useFetchers()` subscribes to the whole live collection. Imports only
5
+ * `react` (never `react-dom/*`), so route components can use it on the server *and* client without
6
+ * dragging either DOM build into the wrong bundle. No JSX (the package builds with plain `tsc`).
7
+ *
8
+ * The router that owns the fetchers is the one `mountRouter` hydrated — it registers itself here via
9
+ * `setMountedRouter`. On the server (no mount) there is no router, so the hooks return an idle state
10
+ * (fetchers are client-only); the first client render after `mountRouter` sees the real fetcher, so
11
+ * there's no hydration mismatch.
12
+ */
13
+ import { useSyncExternalStore } from "react";
14
+ // The active client router (set by `mountRouter`). Module-scoped: the browser mounts one app per
15
+ // page, and fetchers never exist on the server. Shared with `client.ts`'s `mountRouter`.
16
+ let mountedRouter;
17
+ /** Register (or clear) the router that owns fetchers — called by `mountRouter`. Not for app use. */
18
+ export function setMountedRouter(router) {
19
+ mountedRouter = router;
20
+ }
21
+ // Stable idle values for the server / pre-mount snapshot (stable refs → no `useSyncExternalStore`
22
+ // loop, no hydration mismatch).
23
+ const IDLE = { pending: false, data: undefined };
24
+ const NO_FETCHERS = [];
25
+ const noopSubscribe = () => () => { };
26
+ const noopAsync = async () => { };
27
+ /**
28
+ * Subscribe to the independent fetcher for `key` (created lazily, stable across renders). Returns its
29
+ * state (`pending`/`data`/`actionData`/`submission`) + `load`/`submit`. Multiple `useFetcher` calls
30
+ * with different keys run concurrently without disturbing the active route or each other.
31
+ */
32
+ export function useFetcher(key) {
33
+ const fetcher = mountedRouter?.fetcher(key);
34
+ const state = useSyncExternalStore(fetcher?.subscribe ?? noopSubscribe, fetcher?.snapshot ?? (() => IDLE), () => IDLE);
35
+ return { ...state, load: fetcher?.load ?? noopAsync, submit: fetcher?.submit ?? noopAsync };
36
+ }
37
+ /**
38
+ * Subscribe to the whole live fetcher collection — for a global busy view (e.g. "3 saving…"). Read
39
+ * each entry's `.snapshot()` for its state. Re-renders whenever any fetcher transitions or a new one
40
+ * is created.
41
+ */
42
+ export function useFetchers() {
43
+ return useSyncExternalStore(mountedRouter?.subscribeFetchers ?? noopSubscribe, mountedRouter?.fetchers ?? (() => NO_FETCHERS), () => NO_FETCHERS);
44
+ }
45
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAE5C,iGAAiG;AACjG,yFAAyF;AACzF,IAAI,aAAuC,CAAA;AAE3C,oGAAoG;AACpG,MAAM,UAAU,gBAAgB,CAAC,MAAgC;IAC/D,aAAa,GAAG,MAAM,CAAA;AACxB,CAAC;AAED,kGAAkG;AAClG,gCAAgC;AAChC,MAAM,IAAI,GAAiB,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,CAAA;AAC9D,MAAM,WAAW,GAAuB,EAAE,CAAA;AAC1C,MAAM,aAAa,GAAG,GAAiB,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAA;AAClD,MAAM,SAAS,GAAG,KAAK,IAAmB,EAAE,GAAE,CAAC,CAAA;AAU/C;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,MAAM,OAAO,GAAG,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;IAC3C,MAAM,KAAK,GAAG,oBAAoB,CAChC,OAAO,EAAE,SAAS,IAAI,aAAa,EACnC,OAAO,EAAE,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EACjC,GAAG,EAAE,CAAC,IAAI,CACX,CAAA;IACD,OAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,CAAA;AAC7F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,oBAAoB,CACzB,aAAa,EAAE,iBAAiB,IAAI,aAAa,EACjD,aAAa,EAAE,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,EAC9C,GAAG,EAAE,CAAC,WAAW,CAClB,CAAA;AACH,CAAC"}
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@nifrajs/web-react/i18n` — React bindings for `@nifrajs/i18n`. `<I18nProvider locale messages>` builds a
3
+ * `Formatter` (memoized) and provides it; `useT()` reads it. Both `locale` + `messages` are
4
+ * serializable, so a loader returns them, the page renders with the negotiated catalog on the server,
5
+ * and the client rebuilds the same formatter from the same props (no mismatch). Imports only `react` +
6
+ * `@nifrajs/i18n`; no JSX (the package builds with plain `tsc`).
7
+ */
8
+ import { type Formatter, type Messages } from "@nifrajs/i18n";
9
+ import { type ReactNode } from "react";
10
+ export interface I18nProviderProps {
11
+ readonly locale: string;
12
+ readonly messages: Messages;
13
+ readonly children?: ReactNode;
14
+ }
15
+ /** Provide a {@link Formatter} (built from `locale` + `messages`) to the subtree. Memoized on
16
+ * `locale`/`messages`, so switching locale rebuilds it and re-renders consumers. */
17
+ export declare function I18nProvider(props: I18nProviderProps): ReactNode;
18
+ /** Read the current {@link Formatter} (`{ locale, t, n, d }`). Throws if no `<I18nProvider>` is above. */
19
+ export declare function useT(): Formatter;
20
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC9E,OAAO,EAAgC,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAA;AAIzF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED;oFACoF;AACpF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,SAAS,CAMhE;AAED,0GAA0G;AAC1G,wBAAgB,IAAI,IAAI,SAAS,CAMhC"}
package/dist/i18n.js ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@nifrajs/web-react/i18n` — React bindings for `@nifrajs/i18n`. `<I18nProvider locale messages>` builds a
3
+ * `Formatter` (memoized) and provides it; `useT()` reads it. Both `locale` + `messages` are
4
+ * serializable, so a loader returns them, the page renders with the negotiated catalog on the server,
5
+ * and the client rebuilds the same formatter from the same props (no mismatch). Imports only `react` +
6
+ * `@nifrajs/i18n`; no JSX (the package builds with plain `tsc`).
7
+ */
8
+ import { createFormatter } from "@nifrajs/i18n";
9
+ import { createContext, createElement, useContext, useMemo } from "react";
10
+ const I18nContext = createContext(null);
11
+ /** Provide a {@link Formatter} (built from `locale` + `messages`) to the subtree. Memoized on
12
+ * `locale`/`messages`, so switching locale rebuilds it and re-renders consumers. */
13
+ export function I18nProvider(props) {
14
+ const formatter = useMemo(() => createFormatter(props.locale, props.messages), [props.locale, props.messages]);
15
+ return createElement(I18nContext.Provider, { value: formatter }, props.children);
16
+ }
17
+ /** Read the current {@link Formatter} (`{ locale, t, n, d }`). Throws if no `<I18nProvider>` is above. */
18
+ export function useT() {
19
+ const formatter = useContext(I18nContext);
20
+ if (formatter === null) {
21
+ throw new Error("[nifra/web-react] useT() must be used within an <I18nProvider>");
22
+ }
23
+ return formatter;
24
+ }
25
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,eAAe,EAAiC,MAAM,eAAe,CAAA;AAC9E,OAAO,EAAE,aAAa,EAAE,aAAa,EAAkB,UAAU,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAEzF,MAAM,WAAW,GAAG,aAAa,CAAmB,IAAI,CAAC,CAAA;AAQzD;oFACoF;AACpF,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,SAAS,GAAG,OAAO,CACvB,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,EACnD,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,CAC/B,CAAA;IACD,OAAO,aAAa,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AAClF,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,IAAI;IAClB,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;IACzC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAA;IACnF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * `@nifrajs/web-react/image` — a CLS-safe responsive React `<Image>`. A thin wrapper over
3
+ * `resolveImage` from `@nifrajs/image`: it computes `src`/`srcSet`/`sizes`/`width`/`height`/`loading`/
4
+ * `decoding`/`fetchpriority` and renders a plain `<img>`, forwarding any extra DOM props
5
+ * (`className`, `style`, `id`, event handlers, `data-*`, …). Resizing is delegated to the `loader`
6
+ * (an image CDN); nifra bundles no codec. Imports only `react` + `@nifrajs/image`; no JSX.
7
+ */
8
+ import { type ImageLoader, type ImageProps } from "@nifrajs/image";
9
+ import { type ComponentPropsWithoutRef, type ReactElement } from "react";
10
+ /** Attributes `resolveImage` computes — callers can't override them via DOM passthrough. */
11
+ type ComputedAttrs = "src" | "srcSet" | "sizes" | "width" | "height" | "alt" | "loading" | "decoding" | "fetchPriority";
12
+ export interface ImageComponentProps extends ImageProps, Omit<ComponentPropsWithoutRef<"img">, ComputedAttrs | keyof ImageProps> {
13
+ /** CDN URL builder. Defaults to the identity loader (no transform — still CLS-safe + lazy). */
14
+ readonly loader?: ImageLoader;
15
+ /** `data-*` attributes pass through to the `<img>` (React doesn't auto-allow these on a custom
16
+ * component, so they're declared explicitly). */
17
+ readonly [key: `data-${string}`]: string | number | boolean | undefined;
18
+ }
19
+ /**
20
+ * Render a responsive, CLS-safe `<img>`. `width`+`height` are required (reserve layout space);
21
+ * `priority` marks the LCP image (`eager` + `fetchpriority="high"`). Extra DOM props pass through.
22
+ */
23
+ export declare function Image(props: ImageComponentProps): ReactElement;
24
+ export {};
25
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAgB,MAAM,gBAAgB,CAAA;AAChF,OAAO,EAAE,KAAK,wBAAwB,EAAiB,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AAEvF,4FAA4F;AAC5F,KAAK,aAAa,GACd,KAAK,GACL,QAAQ,GACR,OAAO,GACP,OAAO,GACP,QAAQ,GACR,KAAK,GACL,SAAS,GACT,UAAU,GACV,eAAe,CAAA;AAEnB,MAAM,WAAW,mBACf,SAAQ,UAAU,EAChB,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,UAAU,CAAC;IACzE,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B;qDACiD;IACjD,QAAQ,EAAE,GAAG,EAAE,QAAQ,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CACxE;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,mBAAmB,GAAG,YAAY,CAM9D"}
package/dist/image.js ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@nifrajs/web-react/image` — a CLS-safe responsive React `<Image>`. A thin wrapper over
3
+ * `resolveImage` from `@nifrajs/image`: it computes `src`/`srcSet`/`sizes`/`width`/`height`/`loading`/
4
+ * `decoding`/`fetchpriority` and renders a plain `<img>`, forwarding any extra DOM props
5
+ * (`className`, `style`, `id`, event handlers, `data-*`, …). Resizing is delegated to the `loader`
6
+ * (an image CDN); nifra bundles no codec. Imports only `react` + `@nifrajs/image`; no JSX.
7
+ */
8
+ import { resolveImage } from "@nifrajs/image";
9
+ import { createElement } from "react";
10
+ /**
11
+ * Render a responsive, CLS-safe `<img>`. `width`+`height` are required (reserve layout space);
12
+ * `priority` marks the LCP image (`eager` + `fetchpriority="high"`). Extra DOM props pass through.
13
+ */
14
+ export function Image(props) {
15
+ // Strip the nifra-only props (not valid DOM attributes) before forwarding the rest to <img>.
16
+ const { loader, widths, quality, priority, ...rest } = props;
17
+ const resolved = resolveImage(props, loader);
18
+ // `resolved` wins over the raw src/width/height/alt/sizes/loading still present in `rest`.
19
+ return createElement("img", { ...rest, ...resolved });
20
+ }
21
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAqC,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAChF,OAAO,EAAiC,aAAa,EAAqB,MAAM,OAAO,CAAA;AAwBvF;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,KAA0B;IAC9C,6FAA6F;IAC7F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAA;IAC5D,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAC5C,2FAA2F;IAC3F,OAAO,aAAa,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAA;AACvD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { RenderAdapter } from "@nifrajs/web";
2
+ /** The React server render adapter — pass to @nifrajs/web's `renderPage`. */
3
+ export declare const reactAdapter: RenderAdapter;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AASjD,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,aAmB1B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @nifrajs/web-react — the React render adapter for @nifrajs/web (server side). Streaming SSR via
3
+ * `renderToReadableStream`; the layout-chain fold is in `./compose`. Client hydration lives in
4
+ * `@nifrajs/web-react/client`. React's JSX is Bun-native — no build plugin needed.
5
+ */
6
+ import { renderToString as reactRenderToString, renderToReadableStream } from "react-dom/server";
7
+ import { compose } from "./compose.js";
8
+ /** The React server render adapter — pass to @nifrajs/web's `renderPage`. */
9
+ export const reactAdapter = {
10
+ // Synchronous one-pass render for non-deferred pages (renderPage's buffered fast path). React's
11
+ // `renderToString` emits the same hydration-compatible markup as the Fizz stream for non-Suspense
12
+ // content and is markedly cheaper than `renderToReadableStream` — most visibly on Bun, where Fizz's
13
+ // streaming machinery is the heaviest. A page that defer()s/Suspends takes `renderToStream` below.
14
+ renderToString(chain, props) {
15
+ return reactRenderToString(compose(chain, props));
16
+ },
17
+ renderToStream(chain, props) {
18
+ // Resolves a Web `ReadableStream<Uint8Array>` once the shell is renderable; Suspense
19
+ // boundaries stream as they resolve. No `bootstrapModules` — nifra injects the client entry in
20
+ // the document tail. React's default `onError` logs to console.error (errors aren't swallowed).
21
+ return renderToReadableStream(compose(chain, props));
22
+ },
23
+ // React reconciles against the existing DOM on hydrate, so no per-document bootstrap
24
+ // script is needed (contrast Solid's generateHydrationScript) — the seam allows both.
25
+ hydrationHead() {
26
+ return "";
27
+ },
28
+ };
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA;;;;GAIG;AACH,OAAO,EAAE,cAAc,IAAI,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AAChG,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,6EAA6E;AAC7E,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,gGAAgG;IAChG,kGAAkG;IAClG,oGAAoG;IACpG,mGAAmG;IACnG,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,OAAO,mBAAmB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IACnD,CAAC;IACD,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,qFAAqF;QACrF,+FAA+F;QAC/F,gGAAgG;QAChG,OAAO,sBAAsB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IACtD,CAAC;IACD,qFAAqF;IACrF,sFAAsF;IACtF,aAAa;QACX,OAAO,EAAE,CAAA;IACX,CAAC;CACF,CAAA"}
@@ -0,0 +1,27 @@
1
+ import type { IslandStrategy } from "@nifrajs/web/islands";
2
+ /**
3
+ * `@nifrajs/web-react/island` — the `<Island>` marker for no-framework islands. Server-renders its
4
+ * children inside a `<nifra-island>` element that the framework-agnostic `mountIslands` runtime
5
+ * (`@nifrajs/web/islands`) picks up on the client. Imports only `react` (+ a type) — no `react-dom`,
6
+ * no JSX (the package builds with plain `tsc`).
7
+ *
8
+ * Use on a route with `export const hydrate = false`: the page ships zero framework JS, and only the
9
+ * island bundle (your enhancers + the tiny runtime) loads via the route's `islandScripts`. The island's
10
+ * SSR markup is real content (works with JS off); the enhancer upgrades it when it loads.
11
+ */
12
+ import { type ReactNode } from "react";
13
+ export interface IslandProps {
14
+ /** Stable id matching the key in the client's `mountIslands({ <id>: enhancer })`. */
15
+ readonly id: string;
16
+ /** Serializable props handed to the enhancer (JSON-encoded inline in `data-props`; no closures). */
17
+ readonly props?: unknown;
18
+ /** When the enhancer runs (default `load`). `visible` (IntersectionObserver) wins TTI on long pages. */
19
+ readonly strategy?: IslandStrategy;
20
+ /** The island's server-rendered content — real HTML, present with JS off. */
21
+ readonly children?: ReactNode;
22
+ }
23
+ /** Render a `<nifra-island>` marker around server-rendered `children`. The client enhancer mounts the
24
+ * interactivity (see `mountIslands`). Props are JSON-encoded into `data-props`; React escapes the
25
+ * attribute value and the client reads the decoded `dataset.props`. */
26
+ export declare function Island({ id, props, strategy, children }: IslandProps): ReactNode;
27
+ //# sourceMappingURL=island.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island.d.ts","sourceRoot":"","sources":["../src/island.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAC1D;;;;;;;;;GASG;AACH,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAErD,MAAM,WAAW,WAAW;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,oGAAoG;IACpG,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB,wGAAwG;IACxG,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED;;uEAEuE;AACvE,wBAAgB,MAAM,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,QAAiB,EAAE,QAAQ,EAAE,EAAE,WAAW,GAAG,SAAS,CAWzF"}