@nifrajs/web-preact 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.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/await.d.ts +18 -0
- package/dist/await.d.ts.map +1 -0
- package/dist/await.js +52 -0
- package/dist/await.js.map +1 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +40 -0
- package/dist/client.js.map +1 -0
- package/dist/compose.d.ts +10 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +22 -0
- package/dist/compose.js.map +1 -0
- package/dist/content.d.ts +18 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +20 -0
- package/dist/content.js.map +1 -0
- package/dist/error.d.ts +8 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +32 -0
- package/dist/error.js.map +1 -0
- package/dist/fetcher.d.ts +23 -0
- package/dist/fetcher.d.ts.map +1 -0
- package/dist/fetcher.js +48 -0
- package/dist/fetcher.js.map +1 -0
- package/dist/i18n.d.ts +20 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +31 -0
- package/dist/i18n.js.map +1 -0
- package/dist/image.d.ts +22 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +23 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/query.d.ts +17 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +58 -0
- package/dist/query.js.map +1 -0
- package/dist/read-deferred.d.ts +15 -0
- package/dist/read-deferred.d.ts.map +1 -0
- package/dist/read-deferred.js +18 -0
- package/dist/read-deferred.js.map +1 -0
- package/package.json +93 -0
- package/src/await.ts +78 -0
- package/src/client.ts +43 -0
- package/src/compose.ts +23 -0
- package/src/content.ts +28 -0
- package/src/error.ts +33 -0
- package/src/fetcher.ts +67 -0
- package/src/i18n.ts +47 -0
- package/src/image.ts +42 -0
- package/src/index.ts +30 -0
- package/src/query.ts +74 -0
- package/src/read-deferred.ts +32 -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-preact
|
|
2
|
+
|
|
3
|
+
Preact render adapter for @nifrajs/web — streaming SSR + hydration (no build plugin).
|
|
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-preact
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Docs
|
|
14
|
+
|
|
15
|
+
- Reference: <https://nifra.dev/docs>
|
|
16
|
+
- AI-readable: <https://nifra.dev/llms.txt>
|
|
17
|
+
|
|
18
|
+
MIT
|
package/dist/await.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Deferred } from "@nifrajs/web";
|
|
2
|
+
import type { ComponentChildren, VNode } from "preact";
|
|
3
|
+
export interface AwaitProps<T> {
|
|
4
|
+
/** A `Deferred<T>` from a loader's `defer(...)`, or an already-resolved value (client nav). */
|
|
5
|
+
readonly resolve: Deferred<T> | T;
|
|
6
|
+
readonly fallback?: ComponentChildren;
|
|
7
|
+
/** Rendered if the deferred rejects (the streamed `__nifraReject`). Without it the error
|
|
8
|
+
* propagates to the nearest error boundary. */
|
|
9
|
+
readonly errorFallback?: (error: unknown) => ComponentChildren;
|
|
10
|
+
readonly children: (value: T) => ComponentChildren;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Render deferred loader data: show `fallback` until the `Deferred` settles (streamed in by the
|
|
14
|
+
* server), then `children(value)`. An already-resolved `resolve` (a client navigation awaited it)
|
|
15
|
+
* renders immediately. Pairs with a loader's `defer(...)`.
|
|
16
|
+
*/
|
|
17
|
+
export declare function Await<T>(props: AwaitProps<T>): VNode | ComponentChildren;
|
|
18
|
+
//# 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,OAAO,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAgBtD,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,iBAAiB,CAAA;IACrC;mDAC+C;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,iBAAiB,CAAA;IAC9D,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,iBAAiB,CAAA;CACnD;AAiCD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,iBAAiB,CAcxE"}
|
package/dist/await.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/await` — the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
|
|
3
|
+
* and imports only `preact`/`preact/compat` + a type from `@nifrajs/web` (never the server renderer), so
|
|
4
|
+
* route components can use it without dragging server code into the client bundle. No JSX (the package
|
|
5
|
+
* builds with plain `tsc`).
|
|
6
|
+
*
|
|
7
|
+
* Preact's compat has no React 19 `use()` hook, so `readDeferred` (in `./read-deferred`) reads the
|
|
8
|
+
* React-style thenable the core tags (`status`/`value`/`reason`, via the streamed
|
|
9
|
+
* `__nifraResolve`/`__nifraReject`) synchronously: on the client a settled deferred renders its content
|
|
10
|
+
* directly into the boundary the server streamed (no re-suspend, no fallback flash). While pending it
|
|
11
|
+
* throws the promise so `<Suspense>` streams the fallback (server) / shows it until settle (client).
|
|
12
|
+
*/
|
|
13
|
+
import { Component, createElement, Suspense } from "preact/compat";
|
|
14
|
+
import { readDeferred } from "./read-deferred.js";
|
|
15
|
+
// Catches the rejection `readDeferred` throws when the deferred rejects → renders `errorFallback`. A
|
|
16
|
+
// class is required (error boundaries have no hook form). Local so `<Await>` is self-contained.
|
|
17
|
+
class AwaitBoundary extends Component {
|
|
18
|
+
state = { caught: false, error: undefined };
|
|
19
|
+
static getDerivedStateFromError(error) {
|
|
20
|
+
return { caught: true, error };
|
|
21
|
+
}
|
|
22
|
+
render() {
|
|
23
|
+
return this.state.caught ? this.props.fallback(this.state.error) : this.props.children;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isDeferred(value) {
|
|
27
|
+
return (typeof value === "object" &&
|
|
28
|
+
value !== null &&
|
|
29
|
+
value.__nifra_deferred === true);
|
|
30
|
+
}
|
|
31
|
+
// Type-erased (non-generic) so `createElement` infers cleanly; `Await<T>` casts at the call site.
|
|
32
|
+
function Resolved(props) {
|
|
33
|
+
return props.render(readDeferred(props.marker.promise));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Render deferred loader data: show `fallback` until the `Deferred` settles (streamed in by the
|
|
37
|
+
* server), then `children(value)`. An already-resolved `resolve` (a client navigation awaited it)
|
|
38
|
+
* renders immediately. Pairs with a loader's `defer(...)`.
|
|
39
|
+
*/
|
|
40
|
+
export function Await(props) {
|
|
41
|
+
if (!isDeferred(props.resolve))
|
|
42
|
+
return props.children(props.resolve);
|
|
43
|
+
const suspense = createElement(Suspense, { fallback: props.fallback }, createElement(Resolved, {
|
|
44
|
+
marker: props.resolve,
|
|
45
|
+
// safe: the registry promise resolves to T; Resolved is type-erased to satisfy createElement.
|
|
46
|
+
render: props.children,
|
|
47
|
+
}));
|
|
48
|
+
return props.errorFallback
|
|
49
|
+
? createElement(AwaitBoundary, { fallback: props.errorFallback }, suspense)
|
|
50
|
+
: suspense;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=await.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"await.js","sourceRoot":"","sources":["../src/await.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,EAAE,YAAY,EAAiB,MAAM,oBAAoB,CAAA;AAYhE,qGAAqG;AACrG,gGAAgG;AAChG,MAAM,aAAc,SAAQ,SAG3B;IACU,KAAK,GAAwC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;IACzF,MAAM,CAAU,wBAAwB,CAAC,KAAc;QACrD,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,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,OAA4B,CAAC,CAAC,CAAA;AAC9E,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,QAAiD;KAChE,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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { MountRouterOptions, RenderProps } from "@nifrajs/web";
|
|
2
|
+
export { errorBoundary } from "./error.js";
|
|
3
|
+
/** Hydrate a server-rendered Preact 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 Preact Router. `useSyncExternalStore` (preact/compat) subscribes to the
|
|
7
|
+
* agnostic store and re-renders the matched layout chain on each store change — so client
|
|
8
|
+
* navigations swap routes without a full reload. Preact's compat `useSyncExternalStore` is 2-arg
|
|
9
|
+
* (no `getServerSnapshot`); `router.snapshot` is deterministic, so hydration matches the SSR markup.
|
|
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;AAenE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,oGAAoG;AACpG,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,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nifrajs/web-preact/client — Preact client runtime. `hydrate` hydrates a single SSR'd route;
|
|
3
|
+
* `mountRouter` hydrates a stateful Router that subscribes to the agnostic store (via
|
|
4
|
+
* `useSyncExternalStore` from preact/compat) and re-renders the matched chain on every client
|
|
5
|
+
* navigation (no full reload). Kept in its own entry so server code (preact-render-to-string) stays
|
|
6
|
+
* out of the client bundle.
|
|
7
|
+
*/
|
|
8
|
+
import { h, hydrate as preactHydrate } from "preact";
|
|
9
|
+
import { useSyncExternalStore } from "preact/compat";
|
|
10
|
+
import { compose } from "./compose.js";
|
|
11
|
+
import { setMountedRouter } from "./fetcher.js";
|
|
12
|
+
// The `_error` boundary chain element — defined in its own (DOM-free) module, re-exported here so
|
|
13
|
+
// nifra's client codegen resolves it from `@nifrajs/web-preact/client` alongside `mountRouter`.
|
|
14
|
+
export { errorBoundary } from "./error.js";
|
|
15
|
+
/** Hydrate a server-rendered Preact layout `chain` (with the loader `props`) inside `container`. */
|
|
16
|
+
export function hydrate(chain, props, container) {
|
|
17
|
+
preactHydrate(compose(chain, props), container);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Hydrate a stateful Preact Router. `useSyncExternalStore` (preact/compat) subscribes to the
|
|
21
|
+
* agnostic store and re-renders the matched layout chain on each store change — so client
|
|
22
|
+
* navigations swap routes without a full reload. Preact's compat `useSyncExternalStore` is 2-arg
|
|
23
|
+
* (no `getServerSnapshot`); `router.snapshot` is deterministic, so hydration matches the SSR markup.
|
|
24
|
+
*/
|
|
25
|
+
export function mountRouter(options) {
|
|
26
|
+
const { router, routes, container } = options;
|
|
27
|
+
setMountedRouter(router); // expose it to useFetcher/useFetchers (same page, client-only)
|
|
28
|
+
const Router = () => {
|
|
29
|
+
const state = useSyncExternalStore(router.subscribe, router.snapshot);
|
|
30
|
+
return compose(routes[state.routeId] ?? [], {
|
|
31
|
+
data: state.data,
|
|
32
|
+
actionData: state.actionData,
|
|
33
|
+
pending: state.pending,
|
|
34
|
+
// The in-flight submission (for optimistic UI) — spread only when present.
|
|
35
|
+
...(state.submission ? { submission: state.submission } : {}),
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
preactHydrate(h(Router, null), container);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AACH,OAAO,EAA0B,CAAC,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,QAAQ,CAAA;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,kGAAkG;AAClG,gGAAgG;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,oGAAoG;AACpG,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB,EAAE,SAAkB;IACvF,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,SAAoB,CAAC,CAAA;AAC5D,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,CAAC,CAAA;QACrE,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,aAAa,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,SAAoB,CAAC,CAAA;AACtD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RenderProps } from "@nifrajs/web";
|
|
2
|
+
import { type VNode } from "preact";
|
|
3
|
+
/**
|
|
4
|
+
* Fold a layout chain (outermost layout → page) into a single Preact tree: the page (innermost)
|
|
5
|
+
* receives `props` (the loader data); each layout wraps the child via its `children` (Preact passes
|
|
6
|
+
* the 3rd `h` arg as `props.children`). Shared by the server adapter (renderToReadableStream) and
|
|
7
|
+
* the client (hydrate / mountRouter) — the Preact analogue of the React adapter's `compose`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function compose(chain: readonly unknown[], props: RenderProps): VNode;
|
|
10
|
+
//# 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,EAAyB,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE1D;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,KAAK,CAa5E"}
|
package/dist/compose.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
/**
|
|
3
|
+
* Fold a layout chain (outermost layout → page) into a single Preact tree: the page (innermost)
|
|
4
|
+
* receives `props` (the loader data); each layout wraps the child via its `children` (Preact passes
|
|
5
|
+
* the 3rd `h` arg as `props.children`). Shared by the server adapter (renderToReadableStream) and
|
|
6
|
+
* the client (hydrate / mountRouter) — the Preact analogue of the React adapter's `compose`.
|
|
7
|
+
*/
|
|
8
|
+
export function compose(chain, props) {
|
|
9
|
+
const last = chain.length - 1;
|
|
10
|
+
// The chain holds opaque framework components; cast at this plumbing boundary. The page gets the
|
|
11
|
+
// loader props (typed via ComponentType<RenderProps>), then we widen `h`'s VNode<Attributes &
|
|
12
|
+
// RenderProps> to the uniform VNode the fold accumulates into. Preact's VNode<P> is invariant in P
|
|
13
|
+
// under exactOptionalPropertyTypes, so the widen needs the explicit `as VNode` (a comparability
|
|
14
|
+
// cast — Attributes & RenderProps is assignable to {}, so this is not an unsafe coercion).
|
|
15
|
+
let node = h(chain[last], props);
|
|
16
|
+
for (let i = last - 1; i >= 0; i--) {
|
|
17
|
+
// children passed as the 3rd arg — Preact's canonical form (mirrors the React adapter).
|
|
18
|
+
node = h(chain[i], null, node);
|
|
19
|
+
}
|
|
20
|
+
return node;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../src/compose.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,CAAC,EAAc,MAAM,QAAQ,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,KAAyB,EAAE,KAAkB;IACnE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;IAC7B,iGAAiG;IACjG,8FAA8F;IAC9F,mGAAmG;IACnG,gGAAgG;IAChG,2FAA2F;IAC3F,IAAI,IAAI,GAAU,CAAC,CAAC,KAAK,CAAC,IAAI,CAA+B,EAAE,KAAK,CAAU,CAAA;IAC9E,KAAK,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACnC,wFAAwF;QACxF,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;IACjD,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/content` — render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
|
|
3
|
+
* `.html`) into the DOM via `dangerouslySetInnerHTML`. Pairs with content collections:
|
|
4
|
+
* `<Content html={entry.html} />`.
|
|
5
|
+
*
|
|
6
|
+
* Security: injects raw HTML, so `html` MUST be trusted (your own Markdown/content, rendered at
|
|
7
|
+
* build/server time) — never pass user-supplied HTML without sanitizing it first.
|
|
8
|
+
*/
|
|
9
|
+
import { type JSX, type VNode } from "preact";
|
|
10
|
+
export interface ContentProps extends Omit<JSX.HTMLAttributes<HTMLElement>, "dangerouslySetInnerHTML"> {
|
|
11
|
+
/** Trusted, pre-rendered HTML (e.g. `entry.html`). */
|
|
12
|
+
readonly html: string;
|
|
13
|
+
/** Wrapper element (default `"div"`). */
|
|
14
|
+
readonly as?: string;
|
|
15
|
+
}
|
|
16
|
+
/** Render trusted HTML into a wrapper element. Extra props (`class`, `id`, `style`, …) pass through. */
|
|
17
|
+
export declare function Content({ html, as, ...rest }: ContentProps): VNode;
|
|
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,EAAiB,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAE5D,MAAM,WAAW,YACf,SAAQ,IAAI,CAAC,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,yBAAyB,CAAC;IACxE,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wGAAwG;AACxG,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,EAAE,YAAY,GAAG,KAAK,CAQlE"}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/content` — render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
|
|
3
|
+
* `.html`) into the DOM via `dangerouslySetInnerHTML`. Pairs with content collections:
|
|
4
|
+
* `<Content html={entry.html} />`.
|
|
5
|
+
*
|
|
6
|
+
* Security: injects raw HTML, so `html` MUST be trusted (your own Markdown/content, rendered at
|
|
7
|
+
* build/server time) — never pass user-supplied HTML without sanitizing it first.
|
|
8
|
+
*/
|
|
9
|
+
import { createElement } from "preact";
|
|
10
|
+
/** Render trusted HTML into a wrapper element. Extra props (`class`, `id`, `style`, …) pass through. */
|
|
11
|
+
export function Content({ html, as, ...rest }) {
|
|
12
|
+
// Typed intermediate (like `<Image>`) keeps the inferred `VNode` generic stable under
|
|
13
|
+
// exactOptionalPropertyTypes (a bare object literal infers an over-specific, non-assignable VNode).
|
|
14
|
+
const props = {
|
|
15
|
+
...rest,
|
|
16
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
17
|
+
};
|
|
18
|
+
return createElement(as ?? "div", props);
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAwB,MAAM,QAAQ,CAAA;AAU5D,wGAAwG;AACxG,MAAM,UAAU,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,EAAgB;IACzD,sFAAsF;IACtF,oGAAoG;IACpG,MAAM,KAAK,GAAoC;QAC7C,GAAG,IAAI;QACP,uBAAuB,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;KAC1C,CAAA;IACD,OAAO,aAAa,CAAC,EAAE,IAAI,KAAK,EAAE,KAAK,CAAC,CAAA;AAC1C,CAAC"}
|
package/dist/error.d.ts
ADDED
|
@@ -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 Preact error-boundary chain element for nifra's `_error.tsx`. Its own module
|
|
3
|
+
* (imports only `preact`) so it's unit-testable off the DOM and the client codegen can import it from
|
|
4
|
+
* `@nifrajs/web-preact/client` (which re-exports it).
|
|
5
|
+
*/
|
|
6
|
+
import { Component, createElement } from "preact";
|
|
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,EAA0B,aAAa,EAA0B,MAAM,QAAQ,CAAA;AAEjG;;;;;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,CAAU,wBAAwB,CAAC,KAAY;YACnD,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;AAoBvE,oGAAoG;AACpG,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,GAAG,IAAI,CAEvE;AAWD,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,CAOrD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,IAAI,SAAS,OAAO,EAAE,CAKhD"}
|
package/dist/fetcher.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/fetcher` — Preact bindings for concurrent fetchers. `useFetcher(key)` subscribes a
|
|
3
|
+
* component to an independent {@link Fetcher} (via `useSyncExternalStore` from preact/compat) and
|
|
4
|
+
* returns its reactive state plus `load`/`submit`; `useFetchers()` subscribes to the whole live
|
|
5
|
+
* collection. Imports only `preact/compat` + `@nifrajs/web` types, so route components can use it on the
|
|
6
|
+
* server *and* client. 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). Preact's compat `useSyncExternalStore` is 2-arg (no `getServerSnapshot`);
|
|
11
|
+
* with no router the `getSnapshot` already returns idle, so the first client render after `mountRouter`
|
|
12
|
+
* matches the SSR markup — no hydration mismatch.
|
|
13
|
+
*/
|
|
14
|
+
import { useSyncExternalStore } from "preact/compat";
|
|
15
|
+
// The active client router (set by `mountRouter`). Module-scoped: the browser mounts one app per
|
|
16
|
+
// page, and fetchers never exist on the server. Shared with `client.ts`'s `mountRouter`.
|
|
17
|
+
let mountedRouter;
|
|
18
|
+
/** Register (or clear) the router that owns fetchers — called by `mountRouter`. Not for app use. */
|
|
19
|
+
export function setMountedRouter(router) {
|
|
20
|
+
mountedRouter = router;
|
|
21
|
+
}
|
|
22
|
+
// Stable idle values for the server / pre-mount snapshot (stable refs → no `useSyncExternalStore`
|
|
23
|
+
// loop, no hydration mismatch).
|
|
24
|
+
const IDLE = { pending: false, data: undefined };
|
|
25
|
+
const NO_FETCHERS = [];
|
|
26
|
+
const noopSubscribe = () => () => { };
|
|
27
|
+
const noopAsync = async () => { };
|
|
28
|
+
const idleSnapshot = () => IDLE;
|
|
29
|
+
const noFetchers = () => NO_FETCHERS;
|
|
30
|
+
/**
|
|
31
|
+
* Subscribe to the independent fetcher for `key` (created lazily, stable across renders). Returns its
|
|
32
|
+
* state (`pending`/`data`/`actionData`/`submission`) + `load`/`submit`. Multiple `useFetcher` calls
|
|
33
|
+
* with different keys run concurrently without disturbing the active route or each other.
|
|
34
|
+
*/
|
|
35
|
+
export function useFetcher(key) {
|
|
36
|
+
const fetcher = mountedRouter?.fetcher(key);
|
|
37
|
+
const state = useSyncExternalStore(fetcher?.subscribe ?? noopSubscribe, fetcher?.snapshot ?? idleSnapshot);
|
|
38
|
+
return { ...state, load: fetcher?.load ?? noopAsync, submit: fetcher?.submit ?? noopAsync };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to the whole live fetcher collection — for a global busy view (e.g. "3 saving…"). Read
|
|
42
|
+
* each entry's `.snapshot()` for its state. Re-renders whenever any fetcher transitions or a new one
|
|
43
|
+
* is created.
|
|
44
|
+
*/
|
|
45
|
+
export function useFetchers() {
|
|
46
|
+
return useSyncExternalStore(mountedRouter?.subscribeFetchers ?? noopSubscribe, mountedRouter?.fetchers ?? noFetchers);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AAEpD,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;AAC/C,MAAM,YAAY,GAAG,GAAiB,EAAE,CAAC,IAAI,CAAA;AAC7C,MAAM,UAAU,GAAG,GAAuB,EAAE,CAAC,WAAW,CAAA;AAUxD;;;;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,YAAY,CAClC,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,UAAU,CACtC,CAAA;AACH,CAAC"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/i18n` — Preact 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). Uses `preact` +
|
|
6
|
+
* `preact/hooks` — no JSX.
|
|
7
|
+
*/
|
|
8
|
+
import { type Formatter, type Messages } from "@nifrajs/i18n";
|
|
9
|
+
import { type ComponentChildren, type VNode } from "preact";
|
|
10
|
+
export interface I18nProviderProps {
|
|
11
|
+
readonly locale: string;
|
|
12
|
+
readonly messages: Messages;
|
|
13
|
+
readonly children?: ComponentChildren;
|
|
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): VNode;
|
|
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,EAAE,KAAK,iBAAiB,EAAgC,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAezF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CACtC;AAED;oFACoF;AACpF,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,KAAK,CAM5D;AAED,0GAA0G;AAC1G,wBAAgB,IAAI,IAAI,SAAS,CAMhC"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/i18n` — Preact 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). Uses `preact` +
|
|
6
|
+
* `preact/hooks` — no JSX.
|
|
7
|
+
*/
|
|
8
|
+
import { createFormatter } from "@nifrajs/i18n";
|
|
9
|
+
import { createContext, createElement } from "preact";
|
|
10
|
+
import { useContext, useMemo } from "preact/hooks";
|
|
11
|
+
const I18nContext = createContext(null);
|
|
12
|
+
// Preact's `createElement` overloads don't reconcile a typed `Context.Provider` (required `value`)
|
|
13
|
+
// under `exactOptionalPropertyTypes` — overload resolution drops `value` and infers the props as `{}`.
|
|
14
|
+
// The call is correct at runtime (proven by the tests), so invoke it through a precisely-typed element
|
|
15
|
+
// factory that states the real contract (`{ value }` + children), rather than scatter `any`.
|
|
16
|
+
const createProvider = createElement;
|
|
17
|
+
/** Provide a {@link Formatter} (built from `locale` + `messages`) to the subtree. Memoized on
|
|
18
|
+
* `locale`/`messages`, so switching locale rebuilds it and re-renders consumers. */
|
|
19
|
+
export function I18nProvider(props) {
|
|
20
|
+
const formatter = useMemo(() => createFormatter(props.locale, props.messages), [props.locale, props.messages]);
|
|
21
|
+
return createProvider(I18nContext.Provider, { value: formatter }, props.children);
|
|
22
|
+
}
|
|
23
|
+
/** Read the current {@link Formatter} (`{ locale, t, n, d }`). Throws if no `<I18nProvider>` is above. */
|
|
24
|
+
export function useT() {
|
|
25
|
+
const formatter = useContext(I18nContext);
|
|
26
|
+
if (formatter === null) {
|
|
27
|
+
throw new Error("[nifra/web-preact] useT() must be used within an <I18nProvider>");
|
|
28
|
+
}
|
|
29
|
+
return formatter;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/i18n.js.map
ADDED
|
@@ -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,EAA0B,aAAa,EAAE,aAAa,EAAc,MAAM,QAAQ,CAAA;AACzF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAElD,MAAM,WAAW,GAAG,aAAa,CAAmB,IAAI,CAAC,CAAA;AAEzD,mGAAmG;AACnG,uGAAuG;AACvG,uGAAuG;AACvG,6FAA6F;AAC7F,MAAM,cAAc,GAAG,aAIb,CAAA;AAQV;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,cAAc,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;AACnF,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,iEAAiE,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
package/dist/image.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/image` — a CLS-safe responsive Preact `<Image>`. A thin wrapper over `resolveImage`
|
|
3
|
+
* from `@nifrajs/image`: it computes the responsive `<img>` attributes (`src`/`srcset`/`sizes`/`width`/
|
|
4
|
+
* `height`/`loading`/`decoding`/`fetchpriority` — Preact uses lowercase HTML attribute names, so it
|
|
5
|
+
* spreads `toHtmlAttrs`) and forwards any extra DOM props (`class`, `style`, `id`, `data-*`, handlers).
|
|
6
|
+
* Resizing is delegated to the `loader` (an image CDN); nifra bundles no codec. No JSX (`h`).
|
|
7
|
+
*/
|
|
8
|
+
import { type ImageLoader, type ImageProps } from "@nifrajs/image";
|
|
9
|
+
import { type JSX, type VNode } from "preact";
|
|
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<JSX.IntrinsicElements["img"], ComputedAttrs | keyof ImageProps> {
|
|
13
|
+
/** CDN URL builder. Defaults to the identity loader (no transform — still CLS-safe + lazy). */
|
|
14
|
+
readonly loader?: ImageLoader;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Render a responsive, CLS-safe `<img>`. `width`+`height` are required (reserve layout space);
|
|
18
|
+
* `priority` marks the LCP image (`eager` + `fetchpriority="high"`). Extra DOM props pass through.
|
|
19
|
+
*/
|
|
20
|
+
export declare function Image(props: ImageComponentProps): VNode;
|
|
21
|
+
export {};
|
|
22
|
+
//# 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,EAA6B,MAAM,gBAAgB,CAAA;AAC7F,OAAO,EAAK,KAAK,GAAG,EAAE,KAAK,KAAK,EAAE,MAAM,QAAQ,CAAA;AAEhD,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,GAAG,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,aAAa,GAAG,MAAM,UAAU,CAAC;IACtE,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,mBAAmB,GAAG,KAAK,CAQvD"}
|
package/dist/image.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@nifrajs/web-preact/image` — a CLS-safe responsive Preact `<Image>`. A thin wrapper over `resolveImage`
|
|
3
|
+
* from `@nifrajs/image`: it computes the responsive `<img>` attributes (`src`/`srcset`/`sizes`/`width`/
|
|
4
|
+
* `height`/`loading`/`decoding`/`fetchpriority` — Preact uses lowercase HTML attribute names, so it
|
|
5
|
+
* spreads `toHtmlAttrs`) and forwards any extra DOM props (`class`, `style`, `id`, `data-*`, handlers).
|
|
6
|
+
* Resizing is delegated to the `loader` (an image CDN); nifra bundles no codec. No JSX (`h`).
|
|
7
|
+
*/
|
|
8
|
+
import { resolveImage, toHtmlAttrs } from "@nifrajs/image";
|
|
9
|
+
import { h } from "preact";
|
|
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 attrs = toHtmlAttrs(resolveImage(props, loader));
|
|
18
|
+
// `attrs` wins over the raw src/width/height/alt/sizes/loading still present in `rest`. The typed
|
|
19
|
+
// local pins Preact's `h` to its string-tag overload (spreads skip excess-property checks).
|
|
20
|
+
const imgProps = { ...rest, ...attrs };
|
|
21
|
+
return h("img", imgProps);
|
|
22
|
+
}
|
|
23
|
+
//# 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,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC7F,OAAO,EAAE,CAAC,EAAwB,MAAM,QAAQ,CAAA;AAqBhD;;;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,KAAK,GAAG,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAA;IACtD,kGAAkG;IAClG,4FAA4F;IAC5F,MAAM,QAAQ,GAAiC,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,EAAE,CAAA;IACpE,OAAO,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAA;AAC3B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -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;AAYjD,8EAA8E;AAC9E,eAAO,MAAM,aAAa,EAAE,aAgB3B,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nifrajs/web-preact — the Preact render adapter for @nifrajs/web (server side). Streaming SSR via
|
|
3
|
+
* `renderToReadableStream` from `preact-render-to-string/stream` (a Web `ReadableStream<Uint8Array>`,
|
|
4
|
+
* the seam's native shape); the layout-chain fold is in `./compose`. Client hydration lives in
|
|
5
|
+
* `@nifrajs/web-preact/client`. Preact uses `h()` render functions, so no JSX/build plugin is needed
|
|
6
|
+
* (contrast `@nifrajs/web-solid`'s Babel plugin).
|
|
7
|
+
*/
|
|
8
|
+
import { renderToString as preactRenderToString } from "preact-render-to-string";
|
|
9
|
+
import { renderToReadableStream } from "preact-render-to-string/stream";
|
|
10
|
+
import { compose } from "./compose.js";
|
|
11
|
+
/** The Preact server render adapter — pass to @nifrajs/web's `renderPage`. */
|
|
12
|
+
export const preactAdapter = {
|
|
13
|
+
// Synchronous one-pass render for non-deferred pages (renderPage's buffered fast path). The sync
|
|
14
|
+
// `renderToString` emits the same markup as the stream renderer without the chunking machinery.
|
|
15
|
+
renderToString(chain, props) {
|
|
16
|
+
return preactRenderToString(compose(chain, props));
|
|
17
|
+
},
|
|
18
|
+
renderToStream(chain, props) {
|
|
19
|
+
// `renderToReadableStream` yields a Web ReadableStream<Uint8Array>; <Suspense> boundaries
|
|
20
|
+
// (preact/compat) stream as they resolve, mirroring the React adapter.
|
|
21
|
+
return renderToReadableStream(compose(chain, props));
|
|
22
|
+
},
|
|
23
|
+
// Preact reconciles against the existing DOM on hydrate (like React), so there's no per-document
|
|
24
|
+
// bootstrap script — the seam allows the empty string (contrast Solid's generateHydrationScript).
|
|
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;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAA;AAChF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,iGAAiG;IACjG,gGAAgG;IAChG,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,OAAO,oBAAoB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IACpD,CAAC;IACD,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,0FAA0F;QAC1F,uEAAuE;QACvE,OAAO,sBAAsB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IACtD,CAAC;IACD,iGAAiG;IACjG,kGAAkG;IAClG,aAAa;QACX,OAAO,EAAE,CAAA;IACX,CAAC;CACF,CAAA"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type QueryClient, type QueryState } from "@nifrajs/web";
|
|
2
|
+
/** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
|
|
3
|
+
export declare function useQueryClient(): Pick<QueryClient, "invalidateQueries">;
|
|
4
|
+
/** A query's reactive {@link QueryState} plus `isPending` + `refetch`. */
|
|
5
|
+
export interface UseQueryResult<T> extends QueryState<T> {
|
|
6
|
+
/** `status === "pending"` — no data yet (initial load). */
|
|
7
|
+
readonly isPending: boolean;
|
|
8
|
+
/** Force a refetch (ignores `staleTime`). */
|
|
9
|
+
readonly refetch: () => Promise<T>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Subscribe to the keyed query for `key`, fetched via `fn`. Returns `{ status, data, error, isFetching,
|
|
13
|
+
* updatedAt, isPending, refetch }`. Concurrent `useQuery`s with the same key share one cache entry +
|
|
14
|
+
* one in-flight fetch (dedup). Refetches on mount and when the key changes; SSR-idle.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useQuery<T>(key: unknown, fn: () => Promise<T>): UseQueryResult<T>;
|
|
17
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;AAqBrB,wGAAwG;AACxG,wBAAgB,cAAc,IAAI,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAEvE;AAgBD,0EAA0E;AAC1E,MAAM,WAAW,cAAc,CAAC,CAAC,CAAE,SAAQ,UAAU,CAAC,CAAC,CAAC;IACtD,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;CACnC;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAejF"}
|