@nifrajs/web-svelte 3.5.0 → 4.0.2
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/Chain.svelte +6 -1
- package/dist/I18nProvider.svelte +6 -3
- package/dist/I18nProvider.svelte.d.ts +4 -4
- package/dist/Rich.svelte +32 -0
- package/dist/Rich.svelte.d.ts +15 -0
- package/dist/Router.svelte +9 -2
- package/dist/client.js +2 -2
- package/dist/client.js.map +1 -1
- package/dist/i18n.d.ts +5 -2
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +3 -1
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -12
- package/dist/index.js.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +7 -3
- package/dist/plugin.js.map +1 -1
- package/dist/router.d.ts +19 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +26 -0
- package/dist/router.js.map +1 -1
- package/dist/svg.d.ts.map +1 -1
- package/dist/svg.js +18 -2
- package/dist/svg.js.map +1 -1
- package/package.json +9 -6
- package/src/Chain.svelte +6 -1
- package/src/I18nProvider.svelte +6 -3
- package/src/I18nProvider.svelte.d.ts +4 -4
- package/src/Rich.svelte +32 -0
- package/src/Rich.svelte.d.ts +15 -0
- package/src/Router.svelte +9 -2
- package/src/client.ts +2 -2
- package/src/i18n.ts +5 -2
- package/src/index.ts +9 -11
- package/src/plugin.ts +17 -2
- package/src/router.ts +31 -1
- package/src/svg.ts +22 -2
package/dist/Chain.svelte
CHANGED
|
@@ -11,13 +11,18 @@
|
|
|
11
11
|
layout, so the array is already empty by then, and leaving it alone keeps that true if it ever moves.
|
|
12
12
|
-->
|
|
13
13
|
<script>
|
|
14
|
-
import { setContext } from "svelte"
|
|
14
|
+
import { hasContext, setContext } from "svelte"
|
|
15
15
|
import Self from "./Chain.svelte"
|
|
16
16
|
let { chain, props, layoutData } = $props()
|
|
17
17
|
// Provide the validated search as a getter (so `useSearch` reads the current value reactively) - the
|
|
18
18
|
// same value the server put in RenderProps.search, so a query-reading page hydrates with no drift. The
|
|
19
19
|
// key string is kept in sync with router.ts's `useSearch`.
|
|
20
20
|
setContext("@nifrajs/web-svelte:search", () => props.search ?? {})
|
|
21
|
+
// The props `useMatches` reads, provided once by the outermost Chain, where `layoutData` is still
|
|
22
|
+
// whole (each nested Chain holds only its own tail). The key string is kept in sync with router.ts.
|
|
23
|
+
if (!hasContext("@nifrajs/web-svelte:props")) {
|
|
24
|
+
setContext("@nifrajs/web-svelte:props", () => ({ ...props, layoutData }))
|
|
25
|
+
}
|
|
21
26
|
</script>
|
|
22
27
|
|
|
23
28
|
{#if chain.length <= 1}
|
package/dist/I18nProvider.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
I18nProvider.svelte - provides a nifra i18n `Formatter` (built from `locale` + `messages
|
|
2
|
+
I18nProvider.svelte - provides a nifra i18n `Formatter` (built from `locale` + `messages`, with the
|
|
3
|
+
optional `fallback`, `onMissing`, `timeZone` and `numberingSystem` of `createFormatter`) to the
|
|
3
4
|
subtree via Svelte context; consumers read it with `useT()` from `@nifrajs/web-svelte/i18n`. The
|
|
4
5
|
formatter is `$derived`, so switching locale rebuilds it. `locale` + `messages` are serializable, so
|
|
5
6
|
SSR renders the negotiated catalog and the client rebuilds the same formatter on hydrate (no
|
|
@@ -10,9 +11,11 @@
|
|
|
10
11
|
import { createFormatter } from "@nifrajs/i18n"
|
|
11
12
|
import { setContext } from "svelte"
|
|
12
13
|
|
|
13
|
-
let { locale, messages, children } = $props()
|
|
14
|
+
let { locale, messages, fallback, onMissing, timeZone, numberingSystem, children } = $props()
|
|
14
15
|
|
|
15
|
-
const formatter = $derived(
|
|
16
|
+
const formatter = $derived(
|
|
17
|
+
createFormatter(locale, messages, { fallback, onMissing, timeZone, numberingSystem }),
|
|
18
|
+
)
|
|
16
19
|
// A getter (not the value) so consumers read the current `$derived` formatter reactively.
|
|
17
20
|
setContext("@nifrajs/web-svelte:i18n", () => formatter)
|
|
18
21
|
</script>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FormatterOptions, Translation } from "@nifrajs/i18n"
|
|
2
2
|
import type { Component, Snippet } from "svelte"
|
|
3
3
|
|
|
4
4
|
/** Hand-written types for `I18nProvider.svelte` (consumers resolve these via the `./i18n` re-export). */
|
|
5
|
-
export interface I18nProviderProps {
|
|
5
|
+
export interface I18nProviderProps extends FormatterOptions {
|
|
6
6
|
/** The active locale (e.g. `"en"`, `"fr-CA"`). */
|
|
7
7
|
locale: string
|
|
8
|
-
/** The
|
|
9
|
-
messages:
|
|
8
|
+
/** The catalog for `locale`, checked against the registered catalog type when one is declared. */
|
|
9
|
+
messages: Translation
|
|
10
10
|
/** The subtree that reads the formatter via `useT()`. */
|
|
11
11
|
children?: Snippet
|
|
12
12
|
}
|
package/dist/Rich.svelte
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Rich.svelte - renders the catalog message at `key` with its tags (`<b>…</b>`, `<br/>`) drawn by the
|
|
3
|
+
snippets in `tags`, from the formatter `<I18nProvider>` provides. No HTML and no `{@html}`: each
|
|
4
|
+
snippet receives its tag's content as a snippet (`{#snippet b(content)}<strong>{@render
|
|
5
|
+
content()}</strong>{/snippet}`), a tag with no snippet renders its content as text, `<br/>` is a
|
|
6
|
+
`<br>` unless `tags.br` is given, and interpolated `vars` are always text. Plain-JS script; the
|
|
7
|
+
context key is the string i18n.ts's `useT` reads.
|
|
8
|
+
-->
|
|
9
|
+
<script>
|
|
10
|
+
import { rich } from "@nifrajs/i18n/rich"
|
|
11
|
+
import { getContext } from "svelte"
|
|
12
|
+
|
|
13
|
+
let { key, tags = {}, vars } = $props()
|
|
14
|
+
|
|
15
|
+
const formatter = getContext("@nifrajs/web-svelte:i18n")
|
|
16
|
+
if (formatter === undefined) {
|
|
17
|
+
throw new Error("[nifra/web-svelte] <Rich> must be used within an <I18nProvider>")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Each tag becomes a plain `{ tag, chunks }` record; the template below draws it with the caller's
|
|
21
|
+
// snippet. Null prototype, own names only, so `<constructor>` is never a handler.
|
|
22
|
+
const parts = $derived.by(() => {
|
|
23
|
+
const handlers = Object.create(null)
|
|
24
|
+
handlers.br = (chunks) => ({ tag: "br", chunks })
|
|
25
|
+
for (const name of Object.keys(tags)) {
|
|
26
|
+
if (typeof tags[name] === "function") handlers[name] = (chunks) => ({ tag: name, chunks })
|
|
27
|
+
}
|
|
28
|
+
return rich(formatter(), key, handlers, vars)
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#snippet draw(chunks)}{#each chunks as chunk}{#if typeof chunk === "string"}{chunk}{:else}{#snippet content()}{@render draw(chunk.chunks)}{/snippet}{#if Object.hasOwn(tags, chunk.tag) && typeof tags[chunk.tag] === "function"}{@render tags[chunk.tag](content)}{:else}<br />{@render content()}{/if}{/if}{/each}{/snippet}{@render draw(parts)}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MessageKey, RegisteredMessages } from "@nifrajs/i18n"
|
|
2
|
+
import type { Component, Snippet } from "svelte"
|
|
3
|
+
|
|
4
|
+
/** Hand-written types for `Rich.svelte` (consumers resolve these via the `./i18n` re-export). */
|
|
5
|
+
export interface RichProps {
|
|
6
|
+
/** The catalog key, checked against the registered catalog type when one is declared. */
|
|
7
|
+
key: MessageKey<RegisteredMessages>
|
|
8
|
+
/** Snippets by tag name; each receives its tag's content as a snippet to render. */
|
|
9
|
+
tags?: Readonly<Record<string, Snippet<[Snippet]>>>
|
|
10
|
+
/** Values for the message's placeholders - always rendered as text. */
|
|
11
|
+
vars?: Readonly<Record<string, unknown>>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare const Rich: Component<RichProps>
|
|
15
|
+
export default Rich
|
package/dist/Router.svelte
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// browser-safe half, and it is where every other adapter takes this from.
|
|
16
16
|
import { searchOfChain } from "@nifrajs/web/client"
|
|
17
17
|
import Chain from "./Chain.svelte"
|
|
18
|
-
let { router, routes, searchSchemas } = $props()
|
|
18
|
+
let { router, routes, searchSchemas, matchChains } = $props()
|
|
19
19
|
|
|
20
20
|
// Capturing the INITIAL `router` is the intent, not an oversight: one router instance is created
|
|
21
21
|
// once per page and mounted here, so it never changes identity for this component's lifetime, and
|
|
@@ -35,11 +35,18 @@
|
|
|
35
35
|
data: snapshot.data,
|
|
36
36
|
actionData: snapshot.actionData,
|
|
37
37
|
pending: snapshot.pending,
|
|
38
|
+
// The same `params`/`path` the server render received, so `useMatches` reports the same chain.
|
|
39
|
+
params: snapshot.params,
|
|
40
|
+
path: snapshot.path,
|
|
38
41
|
search: searchOfChain(searchSchemas?.[snapshot.routeId] ?? [], q === -1 ? "" : snapshot.path.slice(q)),
|
|
39
42
|
...(snapshot.submission ? { submission: snapshot.submission } : {}),
|
|
40
43
|
...(snapshot.boundaries !== undefined ? { boundaries: snapshot.boundaries } : {}),
|
|
44
|
+
// The chain `useMatches` reports - the server passed the same one to the SSR render.
|
|
45
|
+
...(matchChains?.[snapshot.routeId] !== undefined ? { matchChain: matchChains[snapshot.routeId] } : {}),
|
|
41
46
|
}
|
|
42
47
|
})
|
|
43
48
|
</script>
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
<!-- Each layout's own loader data, from the same snapshot. Without it a layout renders `data: null` on
|
|
51
|
+
the client while the server rendered it with its data. -->
|
|
52
|
+
<Chain {chain} {props} layoutData={snapshot.layoutData} />
|
package/dist/client.js
CHANGED
|
@@ -47,7 +47,7 @@ export const hydrationAssuranceHook = Object.freeze({
|
|
|
47
47
|
* matched route), so hydration reconciles cleanly.
|
|
48
48
|
*/
|
|
49
49
|
export function mountRouter(options) {
|
|
50
|
-
const { router, routes, searchSchemas, container } = options;
|
|
50
|
+
const { router, routes, searchSchemas, matchChains, container } = options;
|
|
51
51
|
setMountedRouter(router); // expose it to useFetcher/useFetchers (same page, client-only)
|
|
52
52
|
const warning = assuranceWarning();
|
|
53
53
|
const originalWarn = console.warn;
|
|
@@ -56,7 +56,7 @@ export function mountRouter(options) {
|
|
|
56
56
|
try {
|
|
57
57
|
svelteHydrate(Router, {
|
|
58
58
|
target: container,
|
|
59
|
-
props: { router, routes, searchSchemas },
|
|
59
|
+
props: { router, routes, searchSchemas, matchChains },
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
finally {
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,KAAK,MAAM,gBAAgB,CAAA;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,MAAM,MAAM,iBAAiB,CAAA;AAEpC,qGAAqG;AACrG,gGAAgG;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAO1C,SAAS,gBAAgB;IACvB,MAAM,KAAK,GAAI,UAAsD,CACnE,MAAM,CAAC,GAAG,CAAC,2BAA2B,CAAC,CACxC,CAAA;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACjE,MAAM,QAAQ,GAAI,KAAiC,CAAC,SAAS,CAAA;IAC7D,OAAO,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAE,QAAsC,CAAC,CAAC,CAAC,SAAS,CAAA;AAC7F,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,KAAyB,EACzB,KAAkB,EAClB,SAAkB,EAClB,OAAmC;IAEnC,MAAM,OAAO,GAAG,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAAE,CAAA;IACxD,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAA;IACjC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IAC1F,IAAI,CAAC;QACH,aAAa,CAAC,KAAK,EAAE;YACnB,MAAM,EAAE,SAAoB;YAC5B,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE;SACtD,CAAC,CAAA;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,GAAG,YAAY,CAAA;IACxD,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC;IAClD,SAAS,EAAE,QAAiB;IAC5B,eAAe,EAAE,GAAW,EAAE,CAAC,aAAa;CAC7C,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AACH,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,QAAQ,CAAA;AACjD,OAAO,KAAK,MAAM,gBAAgB,CAAA;AAClC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,MAAM,MAAM,iBAAiB,CAAA;AAEpC,qGAAqG;AACrG,gGAAgG;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAO1C,SAAS,gBAAgB;IACvB,MAAM,KAAK,GAAI,UAAsD,CACnE,MAAM,CAAC,GAAG,CAAC,2BAA2B,CAAC,CACxC,CAAA;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACjE,MAAM,QAAQ,GAAI,KAAiC,CAAC,SAAS,CAAA;IAC7D,OAAO,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAE,QAAsC,CAAC,CAAC,CAAC,SAAS,CAAA;AAC7F,CAAC;AAED,MAAM,UAAU,OAAO,CACrB,KAAyB,EACzB,KAAkB,EAClB,SAAkB,EAClB,OAAmC;IAEnC,MAAM,OAAO,GAAG,OAAO,EAAE,SAAS,IAAI,gBAAgB,EAAE,CAAA;IACxD,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAA;IACjC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IAC1F,IAAI,CAAC;QACH,aAAa,CAAC,KAAK,EAAE;YACnB,MAAM,EAAE,SAAoB;YAC5B,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE;SACtD,CAAC,CAAA;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,GAAG,YAAY,CAAA;IACxD,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,CAAC;IAClD,SAAS,EAAE,QAAiB;IAC5B,eAAe,EAAE,GAAW,EAAE,CAAC,aAAa;CAC7C,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACrD,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,OAAO,CAAA;IACzE,gBAAgB,CAAC,MAAM,CAAC,CAAA,CAAC,+DAA+D;IACxF,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAA;IAClC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAA;IACjC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;IAC1F,IAAI,CAAC;QACH,aAAa,CAAC,MAAM,EAAE;YACpB,MAAM,EAAE,SAAoB;YAC5B,KAAK,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE;SACtD,CAAC,CAAA;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,GAAG,YAAY,CAAA;IACxD,CAAC;AACH,CAAC"}
|
package/dist/i18n.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@nifrajs/web-svelte/i18n` - Svelte bindings for `@nifrajs/i18n`: the `<I18nProvider>` component (re-exported
|
|
3
3
|
* from `I18nProvider.svelte`) plus `useT()`, which reads the provided `Formatter` from Svelte context.
|
|
4
|
-
* Call `useT()` during a component's initialization (like any `getContext`).
|
|
4
|
+
* Call `useT()` during a component's initialization (like any `getContext`). `<Rich>` renders a
|
|
5
|
+
* message's tags with snippets; `rich()` returns the same message as a parts array.
|
|
5
6
|
*/
|
|
6
7
|
import type { Formatter } from "@nifrajs/i18n";
|
|
8
|
+
export { type RichChunks, type RichTags, rich } from "@nifrajs/i18n/rich";
|
|
7
9
|
export { default as I18nProvider, type I18nProviderProps } from "./I18nProvider.svelte";
|
|
8
|
-
|
|
10
|
+
export { default as Rich, type RichProps } from "./Rich.svelte";
|
|
11
|
+
/** Read the current {@link Formatter} (`{ locale, t, get, n, d }`). Throws if no `<I18nProvider>` is above.
|
|
9
12
|
* nifra switches locale by re-navigating, which re-runs the consuming component with the new catalog. */
|
|
10
13
|
export declare function useT(): Formatter;
|
|
11
14
|
//# sourceMappingURL=i18n.d.ts.map
|
package/dist/i18n.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAG9C,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AACzE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACvF,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,KAAK,SAAS,EAAE,MAAM,eAAe,CAAA;AAM/D;yGACyG;AACzG,wBAAgB,IAAI,IAAI,SAAS,CAMhC"}
|
package/dist/i18n.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { getContext } from "svelte";
|
|
2
|
+
export { rich } from "@nifrajs/i18n/rich";
|
|
2
3
|
export { default as I18nProvider } from "./I18nProvider.svelte";
|
|
4
|
+
export { default as Rich } from "./Rich.svelte";
|
|
3
5
|
// Must match the string key `I18nProvider.svelte` passes to `setContext` (a string avoids a
|
|
4
6
|
// `.svelte` → `.ts` import that wouldn't resolve once the .svelte is copied to dist).
|
|
5
7
|
const I18N_KEY = "@nifrajs/web-svelte:i18n";
|
|
6
|
-
/** Read the current {@link Formatter} (`{ locale, t, n, d }`). Throws if no `<I18nProvider>` is above.
|
|
8
|
+
/** Read the current {@link Formatter} (`{ locale, t, get, n, d }`). Throws if no `<I18nProvider>` is above.
|
|
7
9
|
* nifra switches locale by re-navigating, which re-runs the consuming component with the new catalog. */
|
|
8
10
|
export function useT() {
|
|
9
11
|
const get = getContext(I18N_KEY);
|
package/dist/i18n.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"i18n.js","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAEnC,OAAO,EAAkC,IAAI,EAAE,MAAM,oBAAoB,CAAA;AACzE,OAAO,EAAE,OAAO,IAAI,YAAY,EAA0B,MAAM,uBAAuB,CAAA;AACvF,OAAO,EAAE,OAAO,IAAI,IAAI,EAAkB,MAAM,eAAe,CAAA;AAE/D,4FAA4F;AAC5F,sFAAsF;AACtF,MAAM,QAAQ,GAAG,0BAA0B,CAAA;AAE3C;yGACyG;AACzG,MAAM,UAAU,IAAI;IAClB,MAAM,GAAG,GAAG,UAAU,CAAgC,QAAQ,CAAC,CAAA;IAC/D,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,GAAG,EAAE,CAAA;AACd,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nifrajs/web-svelte - the Svelte 5 render adapter for @nifrajs/web (server side). SSR via Svelte's
|
|
3
|
+
* `render` (svelte/server), which returns HTML strings (Svelte SSR is string-based, not a stream),
|
|
4
|
+
* wrapped in a one-chunk Web `ReadableStream` (the seam's shape). The layout-chain fold is the recursive
|
|
5
|
+
* `Chain.svelte`. Client hydration lives in `@nifrajs/web-svelte/client`. Svelte components compile from
|
|
6
|
+
* `.svelte` files, so this adapter ships a build plugin at `@nifrajs/web-svelte/plugin` (like
|
|
7
|
+
* `@nifrajs/web-solid/plugin`) - there is no callable-component runtime.
|
|
8
|
+
*/
|
|
1
9
|
import type { RenderAdapter } from "@nifrajs/web";
|
|
2
|
-
export { svelteBunPlugin } from "./plugin.js";
|
|
3
10
|
/** The Svelte server render adapter - pass to @nifrajs/web's `renderPage`. */
|
|
4
11
|
export declare const svelteAdapter: RenderAdapter;
|
|
5
12
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,cAAc,CAAA;AAqF9D,8EAA8E;AAC9E,eAAO,MAAM,aAAa,EAAE,aA8B3B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @nifrajs/web-svelte - the Svelte 5 render adapter for @nifrajs/web (server side) + the `.svelte` compiler
|
|
3
|
-
* Bun-plugin. SSR via Svelte's `render` (svelte/server), which returns HTML strings (Svelte SSR is
|
|
4
|
-
* string-based, not a stream), wrapped in a one-chunk Web `ReadableStream` (the seam's shape). The
|
|
5
|
-
* layout-chain fold is the recursive `Chain.svelte`. Client hydration lives in `@nifrajs/web-svelte/client`.
|
|
6
|
-
* Svelte components compile from `.svelte` files, so this adapter ships a build plugin (like
|
|
7
|
-
* `@nifrajs/web-solid`'s Babel plugin) - there is no callable-component runtime.
|
|
8
|
-
*/
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
1
|
import { ssrModuleLoader } from "@nifrajs/web";
|
|
11
2
|
import { render as runtimeRender } from "svelte/server";
|
|
12
|
-
// Re-export the compiler plugin for convenience.
|
|
13
|
-
export { svelteBunPlugin } from "./plugin.js";
|
|
14
3
|
let chainComponent;
|
|
15
4
|
let chainPromise;
|
|
16
5
|
/**
|
|
@@ -32,7 +21,9 @@ const loadChain = () => {
|
|
|
32
21
|
const load = ssrModuleLoader();
|
|
33
22
|
chainPromise ??= (load !== undefined
|
|
34
23
|
? Promise.all([
|
|
35
|
-
|
|
24
|
+
// `import.meta.dirname`, not `node:url`: edge bundles resolve that to a browser polyfill with
|
|
25
|
+
// no `fileURLToPath`, and this module is in every Svelte server bundle.
|
|
26
|
+
load(`${import.meta.dirname}/Chain.svelte`),
|
|
36
27
|
load("svelte/server"),
|
|
37
28
|
]).then(([mod, server]) => {
|
|
38
29
|
render = server.render;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,eAAe,CAAA;AAkCvD,IAAI,cAAiD,CAAA;AACrD,IAAI,YAAwD,CAAA;AAC5D;;;;GAIG;AACH,IAAI,MAAM,GAAyB,aAAa,CAAA;AAChD,sEAAsE;AACtE,MAAM,SAAS,GAAG,GAA2D,EAAE;IAC7E,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,cAAc,CAAA;IACvD,oGAAoG;IACpG,iGAAiG;IACjG,oGAAoG;IACpG,+FAA+F;IAC/F,qGAAqG;IACrG,oFAAoF;IACpF,MAAM,IAAI,GAAG,eAAe,EAAE,CAAA;IAC9B,YAAY,KAAK,CACf,IAAI,KAAK,SAAS;QAChB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;YACV,8FAA8F;YAC9F,wEAAwE;YACxE,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,eAAe,CAAC;YAC3C,IAAI,CAAC,eAAe,CAAC;SACtB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE;YACxB,MAAM,GAAI,MAA2C,CAAC,MAAM,CAAA;YAC5D,OAAQ,GAA0C,CAAC,OAAO,CAAA;QAC5D,CAAC,CAAC;QACJ,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAgC,CAAC,CAC7E,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;QACX,cAAc,GAAG,CAAC,CAAA;QAClB,OAAO,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;IACF,OAAO,YAAY,CAAA;AACrB,CAAC,CAAA;AAED,8FAA8F;AAC9F,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC5C,OAAO,IAAI,cAAc,CAAC;QACxB,KAAK,CAAC,UAAU;YACd,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;YACzB,UAAU,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,kGAAkG;IAClG,uFAAuF;IACvF,mGAAmG;IACnG,mGAAmG;IACnG,kGAAkG;IAClG,oEAAoE;IACpE,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;QACzB,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAA;QAC7D,OAAO,KAAK,YAAY,OAAO;YAC7B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC;YACtD,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAA;IAC3C,CAAC;IACD,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,iGAAiG;QACjG,kGAAkG;QAClG,kGAAkG;QAClG,wDAAwD;QACxD,MAAM,KAAK,GAAG,SAAS,EAAE,CAAA;QACzB,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,CAAA;QAC7D,OAAO,KAAK,YAAY,OAAO;YAC7B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;YAChE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;IACrD,CAAC;IACD,qGAAqG;IACrG,wFAAwF;IACxF,aAAa;QACX,OAAO,EAAE,CAAA;IACX,CAAC;CACF,CAAA"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAsBpC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,OAAO,CAAA;CAAE,CAEtF;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CA4ElE"}
|
package/dist/plugin.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { devHotComponent, devServerCompile, normalizeFilePath, portablePath, rewriteSsrImports, } from "@nifrajs/web/plugins/kit";
|
|
1
|
+
import { devHotComponent, devServerCompile, normalizeFilePath, portablePath, rewriteSsrImports, withDevSourceMap, } from "@nifrajs/web/plugins/kit";
|
|
2
2
|
import { compile } from "svelte/compiler";
|
|
3
3
|
/**
|
|
4
4
|
* `@nifrajs/web-svelte/plugin` - the `.svelte` compiler Bun-plugin, in its OWN module (no `.svelte`
|
|
@@ -81,15 +81,19 @@ export function svelteBunPlugin(generate) {
|
|
|
81
81
|
dev,
|
|
82
82
|
hmr: dev && generate === "dom" && devHotComponent(path),
|
|
83
83
|
});
|
|
84
|
+
const map = { ...js.map, version: 3 };
|
|
84
85
|
if (generate === "dom" && css?.code) {
|
|
85
86
|
cssByPath.set(portablePath(path), css.code);
|
|
86
87
|
// Import the virtual style module so the bundler pulls the scoped CSS into the app stylesheet.
|
|
87
88
|
return {
|
|
88
|
-
contents: `${js.code}\nimport ${JSON.stringify(portablePath(path) + STYLE_SUFFIX)}\n`,
|
|
89
|
+
contents: withDevSourceMap(`${js.code}\nimport ${JSON.stringify(portablePath(path) + STYLE_SUFFIX)}\n`, map, path, generate),
|
|
89
90
|
loader: "js",
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
contents: withDevSourceMap(rewriteSsrImports(js.code, path, generate), map, path, generate),
|
|
95
|
+
loader: "js",
|
|
96
|
+
};
|
|
93
97
|
});
|
|
94
98
|
// Virtual CSS module: `<file>.svelte?svelte-css` → the compiled scoped stylesheet (css loader).
|
|
95
99
|
build.onResolve({ filter: /\?svelte-css$/ }, (args) => ({
|
package/dist/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
|
package/dist/router.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { InferOutput, StandardSchemaV1 } from "@nifrajs/core/server";
|
|
2
|
-
import type { Blocker, BlockerFunction, BlockerState, NavigateFunction } from "@nifrajs/web";
|
|
2
|
+
import type { Blocker, BlockerFunction, BlockerState, NavigateFunction, UIMatch } from "@nifrajs/web";
|
|
3
3
|
/**
|
|
4
4
|
* `@nifrajs/web-svelte/router` - Svelte routing bindings over the agnostic `@nifrajs/web` history layer,
|
|
5
5
|
* as plain `.ts` (no runes/compiler needed): `useNavigate` (programmatic navigation), `useBlocker` (the
|
|
@@ -9,7 +9,7 @@ import type { Blocker, BlockerFunction, BlockerState, NavigateFunction } from "@
|
|
|
9
9
|
* context on SSR + client alike. Idle before hydration (native `<a>` navigation still works). SSR-safe.
|
|
10
10
|
*/
|
|
11
11
|
import { type Readable } from "svelte/store";
|
|
12
|
-
export type { Blocker, BlockerFunction, BlockerState, NavigateFunction };
|
|
12
|
+
export type { Blocker, BlockerFunction, BlockerState, NavigateFunction, UIMatch };
|
|
13
13
|
/**
|
|
14
14
|
* The route's typed, validated search params as a reactive accessor - the SAME value the loader received
|
|
15
15
|
* as `ctx.search`. SSR-correct: `Chain.svelte` provides it from the URL server-side and from the
|
|
@@ -25,6 +25,23 @@ export type { Blocker, BlockerFunction, BlockerState, NavigateFunction };
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
export declare function useSearch<Schema extends StandardSchemaV1 | undefined = undefined>(): () => Schema extends StandardSchemaV1 ? InferOutput<Schema> : Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* The rendered chain - each layout, then the page - with the URL prefix, params and loader data each
|
|
30
|
+
* one owns, plus its `handle` export, as an accessor. The same list on the server render and the client
|
|
31
|
+
* mount, so a layout can render breadcrumbs from its children's `handle`s without a hydration mismatch.
|
|
32
|
+
* Call it in a `$derived` or the template - it updates on navigation.
|
|
33
|
+
*
|
|
34
|
+
* ```svelte
|
|
35
|
+
* <script module>
|
|
36
|
+
* export const handle = { crumb: "Settings" }
|
|
37
|
+
* </script>
|
|
38
|
+
* <script>
|
|
39
|
+
* const matches = useMatches()
|
|
40
|
+
* const crumbs = $derived(matches().flatMap((m) => m.handle?.crumb ?? []))
|
|
41
|
+
* </script>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function useMatches(): () => readonly UIMatch[];
|
|
28
45
|
/** Get the {@link NavigateFunction} (a string path, a history delta, or a typed `{ to, search }` object).
|
|
29
46
|
* Resolves the browser navigate at call time, so it works as soon as `installHistory` has run and no-ops
|
|
30
47
|
* before then / on the server. */
|
package/dist/router.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAIzE,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,YAAY,EACZ,gBAAgB,
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAIzE,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,YAAY,EACZ,gBAAgB,EAIhB,OAAO,EACR,MAAM,cAAc,CAAA;AAYrB;;;;;;;GAOG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAY,MAAM,cAAc,CAAA;AAEtD,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAA;AAQjF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CACvB,MAAM,SAAS,gBAAgB,GAAG,SAAS,GAAG,SAAS,KACpD,MAAM,MAAM,SAAS,gBAAgB,GAAG,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzF;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,IAAI,MAAM,SAAS,OAAO,EAAE,CAGrD;AAED;;kCAEkC;AAClC,wBAAgB,WAAW,IAAI,gBAAgB,CAO9C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,OAAO,GAAG,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC,CAOpF"}
|
package/dist/router.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// `/client`, not the root - these are DOM values, and the root's graph carries the
|
|
2
2
|
// server, which Vite's dev server evaluates rather than tree-shakes.
|
|
3
3
|
import { getBrowserNavigate, IDLE_BLOCKER, registerBlocker, resolveNavigate, } from "@nifrajs/web/client";
|
|
4
|
+
// Its own subpath, so an app that never calls `useMatches` never bundles it.
|
|
5
|
+
import { chainMatches } from "@nifrajs/web/internal/matches-runtime";
|
|
4
6
|
import { getContext } from "svelte";
|
|
5
7
|
/**
|
|
6
8
|
* `@nifrajs/web-svelte/router` - Svelte routing bindings over the agnostic `@nifrajs/web` history layer,
|
|
@@ -34,6 +36,30 @@ export function useSearch() {
|
|
|
34
36
|
const get = getContext(SEARCH_KEY);
|
|
35
37
|
return (get ?? EMPTY_SEARCH_ACCESSOR);
|
|
36
38
|
}
|
|
39
|
+
// Must match the string key the outermost `Chain.svelte` provides its props under.
|
|
40
|
+
const PROPS_KEY = "@nifrajs/web-svelte:props";
|
|
41
|
+
const NO_MATCHES = Object.freeze([]);
|
|
42
|
+
const NO_MATCHES_ACCESSOR = () => NO_MATCHES;
|
|
43
|
+
/**
|
|
44
|
+
* The rendered chain - each layout, then the page - with the URL prefix, params and loader data each
|
|
45
|
+
* one owns, plus its `handle` export, as an accessor. The same list on the server render and the client
|
|
46
|
+
* mount, so a layout can render breadcrumbs from its children's `handle`s without a hydration mismatch.
|
|
47
|
+
* Call it in a `$derived` or the template - it updates on navigation.
|
|
48
|
+
*
|
|
49
|
+
* ```svelte
|
|
50
|
+
* <script module>
|
|
51
|
+
* export const handle = { crumb: "Settings" }
|
|
52
|
+
* </script>
|
|
53
|
+
* <script>
|
|
54
|
+
* const matches = useMatches()
|
|
55
|
+
* const crumbs = $derived(matches().flatMap((m) => m.handle?.crumb ?? []))
|
|
56
|
+
* </script>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function useMatches() {
|
|
60
|
+
const get = getContext(PROPS_KEY);
|
|
61
|
+
return get === undefined ? NO_MATCHES_ACCESSOR : () => chainMatches(get());
|
|
62
|
+
}
|
|
37
63
|
/** Get the {@link NavigateFunction} (a string path, a history delta, or a typed `{ to, search }` object).
|
|
38
64
|
* Resolves the browser navigate at call time, so it works as soon as `installHistory` has run and no-ops
|
|
39
65
|
* before then / on the server. */
|
package/dist/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAcA,mFAAmF;AACnF,qEAAqE;AACrE,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAC5B,6EAA6E;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAA;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC;;;;;;;GAOG;AACH,OAAO,EAAiB,QAAQ,EAAE,MAAM,cAAc,CAAA;AAItD,uGAAuG;AACvG,oEAAoE;AACpE,MAAM,UAAU,GAAG,4BAA4B,CAAA;AAC/C,MAAM,YAAY,GAAsC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AACzE,MAAM,qBAAqB,GAAG,GAA4B,EAAE,CAAC,YAAY,CAAA;AAEzE;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS;IAGvB,MAAM,GAAG,GAAG,UAAU,CAA8C,UAAU,CAAC,CAAA;IAC/E,OAAO,CAAC,GAAG,IAAI,qBAAqB,CAET,CAAA;AAC7B,CAAC;AAED,mFAAmF;AACnF,MAAM,SAAS,GAAG,2BAA2B,CAAA;AAC7C,MAAM,UAAU,GAAuB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AACxD,MAAM,mBAAmB,GAAG,GAAuB,EAAE,CAAC,UAAU,CAAA;AAEhE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,GAAG,GAAG,UAAU,CAAkC,SAAS,CAAC,CAAA;IAClE,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,CAAA;AAC5E,CAAC;AAED;;kCAEkC;AAClC,MAAM,UAAU,WAAW;IACzB,OAAO,CAAC,CAAC,EAAyC,EAAE,OAAyB,EAAE,EAAE;QAC/E,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAA;QACrC,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAM;QAClC,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,EAAE,OAAO,CAAC,CAAA;QAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;IACzC,CAAC,CAAqB,CAAA;AACxB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CAAC,WAAsC;IAC/D,OAAO,QAAQ,CAAU,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAC7C,eAAe,CACb,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,WAAW,KAAK,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAC/E,GAAG,CACJ,CACF,CAAA;AACH,CAAC"}
|
package/dist/svg.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"svg.d.ts","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AASpC,qGAAqG;AACrG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmB/C;AAED;wBACwB;AACxB,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAe9E"}
|
package/dist/svg.js
CHANGED
|
@@ -9,11 +9,27 @@
|
|
|
9
9
|
* a plain `import "./icon.svg"` (asset URL) is untouched - only `?component` matches.
|
|
10
10
|
*/
|
|
11
11
|
import { normalizeFilePath } from "@nifrajs/web/plugins/kit";
|
|
12
|
-
import { SVG_COMPONENT_FILTER,
|
|
12
|
+
import { SVG_COMPONENT_FILTER, svgTemplateMarkup } from "@nifrajs/web/plugins/svg";
|
|
13
13
|
import { compile } from "svelte/compiler";
|
|
14
|
+
/** A brace as a character reference: in Svelte markup, text or an attribute value, it opens an expression. */
|
|
15
|
+
const braces = (text) => text.replace(/[{}]/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
16
|
+
/** Names Svelte reads as directives or special elements, never as SVG. */
|
|
17
|
+
const SVELTE_DIRECTIVE = /^(?:on|bind|use|class|style|transition|in|out|animate|let|attach):/;
|
|
14
18
|
/** Wrap raw SVG XML in a Svelte 5 component: strip XML noise, spread props onto the root `<svg>`. */
|
|
15
19
|
export function svgToSvelte(xml) {
|
|
16
|
-
const cleaned =
|
|
20
|
+
const cleaned = svgTemplateMarkup(xml, {
|
|
21
|
+
text: (text, cdata) => braces(cdata ? text.replaceAll("&", "&").replaceAll("<", "<") : text),
|
|
22
|
+
attributeValue: braces,
|
|
23
|
+
// A nested <style> or <script> is raw text to Svelte, so its braces are not expressions.
|
|
24
|
+
rawText: new Set(["style", "script"]),
|
|
25
|
+
refuse: (name, kind) => kind === "attribute"
|
|
26
|
+
? SVELTE_DIRECTIVE.test(name)
|
|
27
|
+
? `the attribute ${name} is a Svelte directive`
|
|
28
|
+
: undefined
|
|
29
|
+
: name.startsWith("svelte:") || name.includes(".") || /^[A-Z]/.test(name)
|
|
30
|
+
? `Svelte reads the element <${name}> as a component or special element`
|
|
31
|
+
: undefined,
|
|
32
|
+
});
|
|
17
33
|
// `$props()` opts the component into runes (Svelte 5); spread onto the root so user attrs win.
|
|
18
34
|
const withSpread = cleaned.replace(/^(<svg\b[^>]*?)>/, "$1 {...props}>");
|
|
19
35
|
return `<script>const props = $props()</script>\n${withSpread}\n`;
|
package/dist/svg.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svg.js","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"svg.js","sourceRoot":"","sources":["../src/svg.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAElF,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEzC,8GAA8G;AAC9G,MAAM,MAAM,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;AAEpG,0EAA0E;AAC1E,MAAM,gBAAgB,GAAG,oEAAoE,CAAA;AAE7F,qGAAqG;AACrG,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,OAAO,GAAG,iBAAiB,CAAC,GAAG,EAAE;QACrC,IAAI,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACpB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9E,cAAc,EAAE,MAAM;QACtB,yFAAyF;QACzF,OAAO,EAAE,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACrC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CACrB,IAAI,KAAK,WAAW;YAClB,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC3B,CAAC,CAAC,iBAAiB,IAAI,wBAAwB;gBAC/C,CAAC,CAAC,SAAS;YACb,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;gBACvE,CAAC,CAAC,6BAA6B,IAAI,qCAAqC;gBACxE,CAAC,CAAC,SAAS;KAClB,CAAC,CAAA;IACF,+FAA+F;IAC/F,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,CAAA;IACxE,OAAO,4CAA4C,UAAU,IAAI,CAAA;AACnE,CAAC;AAED;wBACwB;AACxB,MAAM,UAAU,2BAA2B,CAAC,QAAuB;IACjE,OAAO;QACL,IAAI,EAAE,oBAAoB,QAAQ,EAAE;QACpC,KAAK,CAAC,KAAK;YACT,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,oBAAoB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBAC5D,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACzC,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;gBACvC,MAAM,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE;oBACvC,QAAQ,EAAE,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;oBAClD,QAAQ,EAAE,IAAI;iBACf,CAAC,CAAA;gBACF,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;YAC5C,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/web-svelte",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Svelte 5 render adapter for @nifrajs/web - SSR + hydration
|
|
3
|
+
"version": "4.0.2",
|
|
4
|
+
"description": "Svelte 5 render adapter for @nifrajs/web - SSR + hydration, with the .svelte compiler Bun plugin (@nifrajs/web-svelte/plugin).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
7
7
|
"typescript",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"full-stack"
|
|
15
15
|
],
|
|
16
16
|
"type": "module",
|
|
17
|
+
"nifra": {
|
|
18
|
+
"environment": "library"
|
|
19
|
+
},
|
|
17
20
|
"main": "./dist/index.js",
|
|
18
21
|
"module": "./dist/index.js",
|
|
19
22
|
"types": "./dist/index.d.ts",
|
|
@@ -100,10 +103,10 @@
|
|
|
100
103
|
"access": "public"
|
|
101
104
|
},
|
|
102
105
|
"dependencies": {
|
|
103
|
-
"@nifrajs/core": "
|
|
104
|
-
"@nifrajs/i18n": "
|
|
105
|
-
"@nifrajs/image": "
|
|
106
|
-
"@nifrajs/web": "
|
|
106
|
+
"@nifrajs/core": "4.0.2",
|
|
107
|
+
"@nifrajs/i18n": "4.0.2",
|
|
108
|
+
"@nifrajs/image": "4.0.2",
|
|
109
|
+
"@nifrajs/web": "4.0.2"
|
|
107
110
|
},
|
|
108
111
|
"peerDependencies": {
|
|
109
112
|
"mdsvex": "^0.12",
|
package/src/Chain.svelte
CHANGED
|
@@ -11,13 +11,18 @@
|
|
|
11
11
|
layout, so the array is already empty by then, and leaving it alone keeps that true if it ever moves.
|
|
12
12
|
-->
|
|
13
13
|
<script>
|
|
14
|
-
import { setContext } from "svelte"
|
|
14
|
+
import { hasContext, setContext } from "svelte"
|
|
15
15
|
import Self from "./Chain.svelte"
|
|
16
16
|
let { chain, props, layoutData } = $props()
|
|
17
17
|
// Provide the validated search as a getter (so `useSearch` reads the current value reactively) - the
|
|
18
18
|
// same value the server put in RenderProps.search, so a query-reading page hydrates with no drift. The
|
|
19
19
|
// key string is kept in sync with router.ts's `useSearch`.
|
|
20
20
|
setContext("@nifrajs/web-svelte:search", () => props.search ?? {})
|
|
21
|
+
// The props `useMatches` reads, provided once by the outermost Chain, where `layoutData` is still
|
|
22
|
+
// whole (each nested Chain holds only its own tail). The key string is kept in sync with router.ts.
|
|
23
|
+
if (!hasContext("@nifrajs/web-svelte:props")) {
|
|
24
|
+
setContext("@nifrajs/web-svelte:props", () => ({ ...props, layoutData }))
|
|
25
|
+
}
|
|
21
26
|
</script>
|
|
22
27
|
|
|
23
28
|
{#if chain.length <= 1}
|
package/src/I18nProvider.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
I18nProvider.svelte - provides a nifra i18n `Formatter` (built from `locale` + `messages
|
|
2
|
+
I18nProvider.svelte - provides a nifra i18n `Formatter` (built from `locale` + `messages`, with the
|
|
3
|
+
optional `fallback`, `onMissing`, `timeZone` and `numberingSystem` of `createFormatter`) to the
|
|
3
4
|
subtree via Svelte context; consumers read it with `useT()` from `@nifrajs/web-svelte/i18n`. The
|
|
4
5
|
formatter is `$derived`, so switching locale rebuilds it. `locale` + `messages` are serializable, so
|
|
5
6
|
SSR renders the negotiated catalog and the client rebuilds the same formatter on hydrate (no
|
|
@@ -10,9 +11,11 @@
|
|
|
10
11
|
import { createFormatter } from "@nifrajs/i18n"
|
|
11
12
|
import { setContext } from "svelte"
|
|
12
13
|
|
|
13
|
-
let { locale, messages, children } = $props()
|
|
14
|
+
let { locale, messages, fallback, onMissing, timeZone, numberingSystem, children } = $props()
|
|
14
15
|
|
|
15
|
-
const formatter = $derived(
|
|
16
|
+
const formatter = $derived(
|
|
17
|
+
createFormatter(locale, messages, { fallback, onMissing, timeZone, numberingSystem }),
|
|
18
|
+
)
|
|
16
19
|
// A getter (not the value) so consumers read the current `$derived` formatter reactively.
|
|
17
20
|
setContext("@nifrajs/web-svelte:i18n", () => formatter)
|
|
18
21
|
</script>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FormatterOptions, Translation } from "@nifrajs/i18n"
|
|
2
2
|
import type { Component, Snippet } from "svelte"
|
|
3
3
|
|
|
4
4
|
/** Hand-written types for `I18nProvider.svelte` (consumers resolve these via the `./i18n` re-export). */
|
|
5
|
-
export interface I18nProviderProps {
|
|
5
|
+
export interface I18nProviderProps extends FormatterOptions {
|
|
6
6
|
/** The active locale (e.g. `"en"`, `"fr-CA"`). */
|
|
7
7
|
locale: string
|
|
8
|
-
/** The
|
|
9
|
-
messages:
|
|
8
|
+
/** The catalog for `locale`, checked against the registered catalog type when one is declared. */
|
|
9
|
+
messages: Translation
|
|
10
10
|
/** The subtree that reads the formatter via `useT()`. */
|
|
11
11
|
children?: Snippet
|
|
12
12
|
}
|
package/src/Rich.svelte
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Rich.svelte - renders the catalog message at `key` with its tags (`<b>…</b>`, `<br/>`) drawn by the
|
|
3
|
+
snippets in `tags`, from the formatter `<I18nProvider>` provides. No HTML and no `{@html}`: each
|
|
4
|
+
snippet receives its tag's content as a snippet (`{#snippet b(content)}<strong>{@render
|
|
5
|
+
content()}</strong>{/snippet}`), a tag with no snippet renders its content as text, `<br/>` is a
|
|
6
|
+
`<br>` unless `tags.br` is given, and interpolated `vars` are always text. Plain-JS script; the
|
|
7
|
+
context key is the string i18n.ts's `useT` reads.
|
|
8
|
+
-->
|
|
9
|
+
<script>
|
|
10
|
+
import { rich } from "@nifrajs/i18n/rich"
|
|
11
|
+
import { getContext } from "svelte"
|
|
12
|
+
|
|
13
|
+
let { key, tags = {}, vars } = $props()
|
|
14
|
+
|
|
15
|
+
const formatter = getContext("@nifrajs/web-svelte:i18n")
|
|
16
|
+
if (formatter === undefined) {
|
|
17
|
+
throw new Error("[nifra/web-svelte] <Rich> must be used within an <I18nProvider>")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Each tag becomes a plain `{ tag, chunks }` record; the template below draws it with the caller's
|
|
21
|
+
// snippet. Null prototype, own names only, so `<constructor>` is never a handler.
|
|
22
|
+
const parts = $derived.by(() => {
|
|
23
|
+
const handlers = Object.create(null)
|
|
24
|
+
handlers.br = (chunks) => ({ tag: "br", chunks })
|
|
25
|
+
for (const name of Object.keys(tags)) {
|
|
26
|
+
if (typeof tags[name] === "function") handlers[name] = (chunks) => ({ tag: name, chunks })
|
|
27
|
+
}
|
|
28
|
+
return rich(formatter(), key, handlers, vars)
|
|
29
|
+
})
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
{#snippet draw(chunks)}{#each chunks as chunk}{#if typeof chunk === "string"}{chunk}{:else}{#snippet content()}{@render draw(chunk.chunks)}{/snippet}{#if Object.hasOwn(tags, chunk.tag) && typeof tags[chunk.tag] === "function"}{@render tags[chunk.tag](content)}{:else}<br />{@render content()}{/if}{/if}{/each}{/snippet}{@render draw(parts)}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MessageKey, RegisteredMessages } from "@nifrajs/i18n"
|
|
2
|
+
import type { Component, Snippet } from "svelte"
|
|
3
|
+
|
|
4
|
+
/** Hand-written types for `Rich.svelte` (consumers resolve these via the `./i18n` re-export). */
|
|
5
|
+
export interface RichProps {
|
|
6
|
+
/** The catalog key, checked against the registered catalog type when one is declared. */
|
|
7
|
+
key: MessageKey<RegisteredMessages>
|
|
8
|
+
/** Snippets by tag name; each receives its tag's content as a snippet to render. */
|
|
9
|
+
tags?: Readonly<Record<string, Snippet<[Snippet]>>>
|
|
10
|
+
/** Values for the message's placeholders - always rendered as text. */
|
|
11
|
+
vars?: Readonly<Record<string, unknown>>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
declare const Rich: Component<RichProps>
|
|
15
|
+
export default Rich
|
package/src/Router.svelte
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// browser-safe half, and it is where every other adapter takes this from.
|
|
16
16
|
import { searchOfChain } from "@nifrajs/web/client"
|
|
17
17
|
import Chain from "./Chain.svelte"
|
|
18
|
-
let { router, routes, searchSchemas } = $props()
|
|
18
|
+
let { router, routes, searchSchemas, matchChains } = $props()
|
|
19
19
|
|
|
20
20
|
// Capturing the INITIAL `router` is the intent, not an oversight: one router instance is created
|
|
21
21
|
// once per page and mounted here, so it never changes identity for this component's lifetime, and
|
|
@@ -35,11 +35,18 @@
|
|
|
35
35
|
data: snapshot.data,
|
|
36
36
|
actionData: snapshot.actionData,
|
|
37
37
|
pending: snapshot.pending,
|
|
38
|
+
// The same `params`/`path` the server render received, so `useMatches` reports the same chain.
|
|
39
|
+
params: snapshot.params,
|
|
40
|
+
path: snapshot.path,
|
|
38
41
|
search: searchOfChain(searchSchemas?.[snapshot.routeId] ?? [], q === -1 ? "" : snapshot.path.slice(q)),
|
|
39
42
|
...(snapshot.submission ? { submission: snapshot.submission } : {}),
|
|
40
43
|
...(snapshot.boundaries !== undefined ? { boundaries: snapshot.boundaries } : {}),
|
|
44
|
+
// The chain `useMatches` reports - the server passed the same one to the SSR render.
|
|
45
|
+
...(matchChains?.[snapshot.routeId] !== undefined ? { matchChain: matchChains[snapshot.routeId] } : {}),
|
|
41
46
|
}
|
|
42
47
|
})
|
|
43
48
|
</script>
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
<!-- Each layout's own loader data, from the same snapshot. Without it a layout renders `data: null` on
|
|
51
|
+
the client while the server rendered it with its data. -->
|
|
52
|
+
<Chain {chain} {props} layoutData={snapshot.layoutData} />
|
package/src/client.ts
CHANGED
|
@@ -61,7 +61,7 @@ export const hydrationAssuranceHook = Object.freeze({
|
|
|
61
61
|
* matched route), so hydration reconciles cleanly.
|
|
62
62
|
*/
|
|
63
63
|
export function mountRouter(options: MountRouterOptions): void {
|
|
64
|
-
const { router, routes, searchSchemas, container } = options
|
|
64
|
+
const { router, routes, searchSchemas, matchChains, container } = options
|
|
65
65
|
setMountedRouter(router) // expose it to useFetcher/useFetchers (same page, client-only)
|
|
66
66
|
const warning = assuranceWarning()
|
|
67
67
|
const originalWarn = console.warn
|
|
@@ -69,7 +69,7 @@ export function mountRouter(options: MountRouterOptions): void {
|
|
|
69
69
|
try {
|
|
70
70
|
svelteHydrate(Router, {
|
|
71
71
|
target: container as Element,
|
|
72
|
-
props: { router, routes, searchSchemas },
|
|
72
|
+
props: { router, routes, searchSchemas, matchChains },
|
|
73
73
|
})
|
|
74
74
|
} finally {
|
|
75
75
|
if (warning !== undefined) console.warn = originalWarn
|
package/src/i18n.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@nifrajs/web-svelte/i18n` - Svelte bindings for `@nifrajs/i18n`: the `<I18nProvider>` component (re-exported
|
|
3
3
|
* from `I18nProvider.svelte`) plus `useT()`, which reads the provided `Formatter` from Svelte context.
|
|
4
|
-
* Call `useT()` during a component's initialization (like any `getContext`).
|
|
4
|
+
* Call `useT()` during a component's initialization (like any `getContext`). `<Rich>` renders a
|
|
5
|
+
* message's tags with snippets; `rich()` returns the same message as a parts array.
|
|
5
6
|
*/
|
|
6
7
|
import type { Formatter } from "@nifrajs/i18n"
|
|
7
8
|
import { getContext } from "svelte"
|
|
8
9
|
|
|
10
|
+
export { type RichChunks, type RichTags, rich } from "@nifrajs/i18n/rich"
|
|
9
11
|
export { default as I18nProvider, type I18nProviderProps } from "./I18nProvider.svelte"
|
|
12
|
+
export { default as Rich, type RichProps } from "./Rich.svelte"
|
|
10
13
|
|
|
11
14
|
// Must match the string key `I18nProvider.svelte` passes to `setContext` (a string avoids a
|
|
12
15
|
// `.svelte` → `.ts` import that wouldn't resolve once the .svelte is copied to dist).
|
|
13
16
|
const I18N_KEY = "@nifrajs/web-svelte:i18n"
|
|
14
17
|
|
|
15
|
-
/** Read the current {@link Formatter} (`{ locale, t, n, d }`). Throws if no `<I18nProvider>` is above.
|
|
18
|
+
/** Read the current {@link Formatter} (`{ locale, t, get, n, d }`). Throws if no `<I18nProvider>` is above.
|
|
16
19
|
* nifra switches locale by re-navigating, which re-runs the consuming component with the new catalog. */
|
|
17
20
|
export function useT(): Formatter {
|
|
18
21
|
const get = getContext<(() => Formatter) | undefined>(I18N_KEY)
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @nifrajs/web-svelte - the Svelte 5 render adapter for @nifrajs/web (server side)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `@nifrajs/web-solid`
|
|
2
|
+
* @nifrajs/web-svelte - the Svelte 5 render adapter for @nifrajs/web (server side). SSR via Svelte's
|
|
3
|
+
* `render` (svelte/server), which returns HTML strings (Svelte SSR is string-based, not a stream),
|
|
4
|
+
* wrapped in a one-chunk Web `ReadableStream` (the seam's shape). The layout-chain fold is the recursive
|
|
5
|
+
* `Chain.svelte`. Client hydration lives in `@nifrajs/web-svelte/client`. Svelte components compile from
|
|
6
|
+
* `.svelte` files, so this adapter ships a build plugin at `@nifrajs/web-svelte/plugin` (like
|
|
7
|
+
* `@nifrajs/web-solid/plugin`) - there is no callable-component runtime.
|
|
8
8
|
*/
|
|
9
|
-
import { fileURLToPath } from "node:url"
|
|
10
9
|
import type { RenderAdapter, RenderProps } from "@nifrajs/web"
|
|
11
10
|
import { ssrModuleLoader } from "@nifrajs/web"
|
|
12
11
|
import type { Component } from "svelte"
|
|
13
12
|
import { render as runtimeRender } from "svelte/server"
|
|
14
13
|
|
|
15
|
-
// Re-export the compiler plugin for convenience.
|
|
16
|
-
export { svelteBunPlugin } from "./plugin.ts"
|
|
17
|
-
|
|
18
14
|
/**
|
|
19
15
|
* `Chain.svelte` is loaded on FIRST RENDER, not at module load.
|
|
20
16
|
*
|
|
@@ -68,7 +64,9 @@ const loadChain = (): Component<ChainProps> | Promise<Component<ChainProps>> =>
|
|
|
68
64
|
chainPromise ??= (
|
|
69
65
|
load !== undefined
|
|
70
66
|
? Promise.all([
|
|
71
|
-
|
|
67
|
+
// `import.meta.dirname`, not `node:url`: edge bundles resolve that to a browser polyfill with
|
|
68
|
+
// no `fileURLToPath`, and this module is in every Svelte server bundle.
|
|
69
|
+
load(`${import.meta.dirname}/Chain.svelte`),
|
|
72
70
|
load("svelte/server"),
|
|
73
71
|
]).then(([mod, server]) => {
|
|
74
72
|
render = (server as { render: typeof runtimeRender }).render
|
package/src/plugin.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
normalizeFilePath,
|
|
5
5
|
portablePath,
|
|
6
6
|
rewriteSsrImports,
|
|
7
|
+
withDevSourceMap,
|
|
7
8
|
} from "@nifrajs/web/plugins/kit"
|
|
8
9
|
import type { BunPlugin } from "bun"
|
|
9
10
|
import { compile } from "svelte/compiler"
|
|
@@ -91,15 +92,29 @@ export function svelteBunPlugin(generate: "dom" | "ssr"): BunPlugin {
|
|
|
91
92
|
dev,
|
|
92
93
|
hmr: dev && generate === "dom" && devHotComponent(path),
|
|
93
94
|
})
|
|
95
|
+
const map = { ...js.map, version: 3 }
|
|
94
96
|
if (generate === "dom" && css?.code) {
|
|
95
97
|
cssByPath.set(portablePath(path), css.code)
|
|
96
98
|
// Import the virtual style module so the bundler pulls the scoped CSS into the app stylesheet.
|
|
97
99
|
return {
|
|
98
|
-
contents:
|
|
100
|
+
contents: withDevSourceMap(
|
|
101
|
+
`${js.code}\nimport ${JSON.stringify(portablePath(path) + STYLE_SUFFIX)}\n`,
|
|
102
|
+
map,
|
|
103
|
+
path,
|
|
104
|
+
generate,
|
|
105
|
+
),
|
|
99
106
|
loader: "js",
|
|
100
107
|
}
|
|
101
108
|
}
|
|
102
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
contents: withDevSourceMap(
|
|
111
|
+
rewriteSsrImports(js.code, path, generate),
|
|
112
|
+
map,
|
|
113
|
+
path,
|
|
114
|
+
generate,
|
|
115
|
+
),
|
|
116
|
+
loader: "js",
|
|
117
|
+
}
|
|
103
118
|
})
|
|
104
119
|
// Virtual CSS module: `<file>.svelte?svelte-css` → the compiled scoped stylesheet (css loader).
|
|
105
120
|
build.onResolve({ filter: /\?svelte-css$/ }, (args) => ({
|
package/src/router.ts
CHANGED
|
@@ -9,6 +9,8 @@ import type {
|
|
|
9
9
|
NavigateFunction,
|
|
10
10
|
NavigateOptions,
|
|
11
11
|
NavigateTargetInput,
|
|
12
|
+
RenderProps,
|
|
13
|
+
UIMatch,
|
|
12
14
|
} from "@nifrajs/web"
|
|
13
15
|
// `/client`, not the root - these are DOM values, and the root's graph carries the
|
|
14
16
|
// server, which Vite's dev server evaluates rather than tree-shakes.
|
|
@@ -18,6 +20,8 @@ import {
|
|
|
18
20
|
registerBlocker,
|
|
19
21
|
resolveNavigate,
|
|
20
22
|
} from "@nifrajs/web/client"
|
|
23
|
+
// Its own subpath, so an app that never calls `useMatches` never bundles it.
|
|
24
|
+
import { chainMatches } from "@nifrajs/web/internal/matches-runtime"
|
|
21
25
|
import { getContext } from "svelte"
|
|
22
26
|
/**
|
|
23
27
|
* `@nifrajs/web-svelte/router` - Svelte routing bindings over the agnostic `@nifrajs/web` history layer,
|
|
@@ -29,7 +33,7 @@ import { getContext } from "svelte"
|
|
|
29
33
|
*/
|
|
30
34
|
import { type Readable, readable } from "svelte/store"
|
|
31
35
|
|
|
32
|
-
export type { Blocker, BlockerFunction, BlockerState, NavigateFunction }
|
|
36
|
+
export type { Blocker, BlockerFunction, BlockerState, NavigateFunction, UIMatch }
|
|
33
37
|
|
|
34
38
|
// Must match the string key `Chain.svelte` passes to `setContext` (a string avoids a `.svelte` → `.ts`
|
|
35
39
|
// import that wouldn't resolve once the .svelte is copied to dist).
|
|
@@ -60,6 +64,32 @@ export function useSearch<
|
|
|
60
64
|
: Record<string, unknown>
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
// Must match the string key the outermost `Chain.svelte` provides its props under.
|
|
68
|
+
const PROPS_KEY = "@nifrajs/web-svelte:props"
|
|
69
|
+
const NO_MATCHES: readonly UIMatch[] = Object.freeze([])
|
|
70
|
+
const NO_MATCHES_ACCESSOR = (): readonly UIMatch[] => NO_MATCHES
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The rendered chain - each layout, then the page - with the URL prefix, params and loader data each
|
|
74
|
+
* one owns, plus its `handle` export, as an accessor. The same list on the server render and the client
|
|
75
|
+
* mount, so a layout can render breadcrumbs from its children's `handle`s without a hydration mismatch.
|
|
76
|
+
* Call it in a `$derived` or the template - it updates on navigation.
|
|
77
|
+
*
|
|
78
|
+
* ```svelte
|
|
79
|
+
* <script module>
|
|
80
|
+
* export const handle = { crumb: "Settings" }
|
|
81
|
+
* </script>
|
|
82
|
+
* <script>
|
|
83
|
+
* const matches = useMatches()
|
|
84
|
+
* const crumbs = $derived(matches().flatMap((m) => m.handle?.crumb ?? []))
|
|
85
|
+
* </script>
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function useMatches(): () => readonly UIMatch[] {
|
|
89
|
+
const get = getContext<(() => RenderProps) | undefined>(PROPS_KEY)
|
|
90
|
+
return get === undefined ? NO_MATCHES_ACCESSOR : () => chainMatches(get())
|
|
91
|
+
}
|
|
92
|
+
|
|
63
93
|
/** Get the {@link NavigateFunction} (a string path, a history delta, or a typed `{ to, search }` object).
|
|
64
94
|
* Resolves the browser navigate at call time, so it works as soon as `installHistory` has run and no-ops
|
|
65
95
|
* before then / on the server. */
|
package/src/svg.ts
CHANGED
|
@@ -10,13 +10,33 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { normalizeFilePath } from "@nifrajs/web/plugins/kit"
|
|
13
|
-
import { SVG_COMPONENT_FILTER,
|
|
13
|
+
import { SVG_COMPONENT_FILTER, svgTemplateMarkup } from "@nifrajs/web/plugins/svg"
|
|
14
14
|
import type { BunPlugin } from "bun"
|
|
15
15
|
import { compile } from "svelte/compiler"
|
|
16
16
|
|
|
17
|
+
/** A brace as a character reference: in Svelte markup, text or an attribute value, it opens an expression. */
|
|
18
|
+
const braces = (text: string): string => text.replace(/[{}]/g, (char) => `&#${char.charCodeAt(0)};`)
|
|
19
|
+
|
|
20
|
+
/** Names Svelte reads as directives or special elements, never as SVG. */
|
|
21
|
+
const SVELTE_DIRECTIVE = /^(?:on|bind|use|class|style|transition|in|out|animate|let|attach):/
|
|
22
|
+
|
|
17
23
|
/** Wrap raw SVG XML in a Svelte 5 component: strip XML noise, spread props onto the root `<svg>`. */
|
|
18
24
|
export function svgToSvelte(xml: string): string {
|
|
19
|
-
const cleaned =
|
|
25
|
+
const cleaned = svgTemplateMarkup(xml, {
|
|
26
|
+
text: (text, cdata) =>
|
|
27
|
+
braces(cdata ? text.replaceAll("&", "&").replaceAll("<", "<") : text),
|
|
28
|
+
attributeValue: braces,
|
|
29
|
+
// A nested <style> or <script> is raw text to Svelte, so its braces are not expressions.
|
|
30
|
+
rawText: new Set(["style", "script"]),
|
|
31
|
+
refuse: (name, kind) =>
|
|
32
|
+
kind === "attribute"
|
|
33
|
+
? SVELTE_DIRECTIVE.test(name)
|
|
34
|
+
? `the attribute ${name} is a Svelte directive`
|
|
35
|
+
: undefined
|
|
36
|
+
: name.startsWith("svelte:") || name.includes(".") || /^[A-Z]/.test(name)
|
|
37
|
+
? `Svelte reads the element <${name}> as a component or special element`
|
|
38
|
+
: undefined,
|
|
39
|
+
})
|
|
20
40
|
// `$props()` opts the component into runes (Svelte 5); spread onto the root so user attrs win.
|
|
21
41
|
const withSpread = cleaned.replace(/^(<svg\b[^>]*?)>/, "$1 {...props}>")
|
|
22
42
|
return `<script>const props = $props()</script>\n${withSpread}\n`
|