@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 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}
@@ -1,5 +1,6 @@
1
1
  <!--
2
- I18nProvider.svelte - provides a nifra i18n `Formatter` (built from `locale` + `messages`) to the
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(createFormatter(locale, messages))
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 { Messages } from "@nifrajs/i18n"
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 ICU message catalog for `locale`. */
9
- messages: 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
  }
@@ -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
@@ -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
- <Chain {chain} {props} />
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 {
@@ -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;IAC5D,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;SACzC,CAAC,CAAA;IACJ,CAAC;YAAS,CAAC;QACT,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,IAAI,GAAG,YAAY,CAAA;IACxD,CAAC;AACH,CAAC"}
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
- /** Read the current {@link Formatter} (`{ locale, t, n, d }`). Throws if no `<I18nProvider>` is above.
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
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAG9C,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAMvF;yGACyG;AACzG,wBAAgB,IAAI,IAAI,SAAS,CAMhC"}
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":"AAMA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAEnC,OAAO,EAAE,OAAO,IAAI,YAAY,EAA0B,MAAM,uBAAuB,CAAA;AAEvF,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"}
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,cAAc,CAAA;AAM9D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAgF7C,8EAA8E;AAC9E,eAAO,MAAM,aAAa,EAAE,aA8B3B,CAAA"}
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
- load(fileURLToPath(new URL("./Chain.svelte", import.meta.url))),
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":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,eAAe,CAAA;AAEvD,iDAAiD;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAkC7C,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,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;YAC/D,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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAOA,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,CA8DlE"}
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 { contents: rewriteSsrImports(js.code, path, generate), loader: "js" };
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) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
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. */
@@ -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,EAGjB,MAAM,cAAc,CAAA;AAUrB;;;;;;;GAOG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAY,MAAM,cAAc,CAAA;AAEtD,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAA;AAQxE;;;;;;;;;;;;;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;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"}
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. */
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAYA,mFAAmF;AACnF,qEAAqE;AACrE,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,qBAAqB,CAAA;AAC5B,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;;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"}
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;AAGpC,qGAAqG;AACrG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAK/C;AAED;wBACwB;AACxB,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAe9E"}
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, stripSvgPreamble } from "@nifrajs/web/plugins/svg";
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 = stripSvgPreamble(xml);
20
+ const cleaned = svgTemplateMarkup(xml, {
21
+ text: (text, cdata) => braces(cdata ? text.replaceAll("&", "&amp;").replaceAll("<", "&lt;") : 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,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAEjF,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEzC,qGAAqG;AACrG,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAA;IACrC,+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"}
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": "3.5.0",
4
- "description": "Svelte 5 render adapter for @nifrajs/web - SSR + hydration + the .svelte compiler Bun-plugin.",
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": "3.5.0",
104
- "@nifrajs/i18n": "3.5.0",
105
- "@nifrajs/image": "3.5.0",
106
- "@nifrajs/web": "3.5.0"
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}
@@ -1,5 +1,6 @@
1
1
  <!--
2
- I18nProvider.svelte - provides a nifra i18n `Formatter` (built from `locale` + `messages`) to the
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(createFormatter(locale, messages))
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 { Messages } from "@nifrajs/i18n"
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 ICU message catalog for `locale`. */
9
- messages: 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
  }
@@ -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
- <Chain {chain} {props} />
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) + 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.
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
- load(fileURLToPath(new URL("./Chain.svelte", import.meta.url))),
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: `${js.code}\nimport ${JSON.stringify(portablePath(path) + STYLE_SUFFIX)}\n`,
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 { contents: rewriteSsrImports(js.code, path, generate), loader: "js" }
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, stripSvgPreamble } from "@nifrajs/web/plugins/svg"
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 = stripSvgPreamble(xml)
25
+ const cleaned = svgTemplateMarkup(xml, {
26
+ text: (text, cdata) =>
27
+ braces(cdata ? text.replaceAll("&", "&amp;").replaceAll("<", "&lt;") : 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`