@contentful/experiences-svelte 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +107 -0
  3. package/dist/ClientExperienceRenderer.svelte +81 -0
  4. package/dist/ClientExperienceRenderer.svelte.d.ts +4 -0
  5. package/dist/ClientExperienceRenderer.svelte.d.ts.map +1 -0
  6. package/dist/MissingComponent.svelte +35 -0
  7. package/dist/MissingComponent.svelte.d.ts +5 -0
  8. package/dist/MissingComponent.svelte.d.ts.map +1 -0
  9. package/dist/NodeRenderer.svelte +83 -0
  10. package/dist/NodeRenderer.svelte.d.ts +15 -0
  11. package/dist/NodeRenderer.svelte.d.ts.map +1 -0
  12. package/dist/NodesRenderer.svelte +51 -0
  13. package/dist/NodesRenderer.svelte.d.ts +14 -0
  14. package/dist/NodesRenderer.svelte.d.ts.map +1 -0
  15. package/dist/ServerExperienceRenderer.svelte +70 -0
  16. package/dist/ServerExperienceRenderer.svelte.d.ts +5 -0
  17. package/dist/ServerExperienceRenderer.svelte.d.ts.map +1 -0
  18. package/dist/WrapWithTemplate.svelte +87 -0
  19. package/dist/WrapWithTemplate.svelte.d.ts +13 -0
  20. package/dist/WrapWithTemplate.svelte.d.ts.map +1 -0
  21. package/dist/component-props.d.ts +18 -0
  22. package/dist/component-props.d.ts.map +1 -0
  23. package/dist/component-props.js +13 -0
  24. package/dist/context.d.ts +20 -0
  25. package/dist/context.d.ts.map +1 -0
  26. package/dist/context.js +61 -0
  27. package/dist/design-utils.d.ts +16 -0
  28. package/dist/design-utils.d.ts.map +1 -0
  29. package/dist/design-utils.js +27 -0
  30. package/dist/get-design-values.d.ts +14 -0
  31. package/dist/get-design-values.d.ts.map +1 -0
  32. package/dist/get-design-values.js +16 -0
  33. package/dist/index.d.ts +21 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +34 -0
  36. package/dist/server-renderer.test.d.ts +2 -0
  37. package/dist/server-renderer.test.d.ts.map +1 -0
  38. package/dist/server-renderer.test.js +566 -0
  39. package/dist/test-fixtures/ButtonFixture.svelte +8 -0
  40. package/dist/test-fixtures/ButtonFixture.svelte.d.ts +7 -0
  41. package/dist/test-fixtures/ButtonFixture.svelte.d.ts.map +1 -0
  42. package/dist/test-fixtures/CapturingComponent.svelte +18 -0
  43. package/dist/test-fixtures/CapturingComponent.svelte.d.ts +4 -0
  44. package/dist/test-fixtures/CapturingComponent.svelte.d.ts.map +1 -0
  45. package/dist/test-fixtures/ContainerFixture.svelte +12 -0
  46. package/dist/test-fixtures/ContainerFixture.svelte.d.ts +8 -0
  47. package/dist/test-fixtures/ContainerFixture.svelte.d.ts.map +1 -0
  48. package/dist/test-fixtures/HeadingFixture.svelte +8 -0
  49. package/dist/test-fixtures/HeadingFixture.svelte.d.ts +7 -0
  50. package/dist/test-fixtures/HeadingFixture.svelte.d.ts.map +1 -0
  51. package/dist/test-fixtures/ItemFixture.svelte +11 -0
  52. package/dist/test-fixtures/ItemFixture.svelte.d.ts +8 -0
  53. package/dist/test-fixtures/ItemFixture.svelte.d.ts.map +1 -0
  54. package/dist/test-fixtures/PrecedenceFixture.svelte +5 -0
  55. package/dist/test-fixtures/PrecedenceFixture.svelte.d.ts +7 -0
  56. package/dist/test-fixtures/PrecedenceFixture.svelte.d.ts.map +1 -0
  57. package/dist/test-fixtures/TemplateFixture.svelte +18 -0
  58. package/dist/test-fixtures/TemplateFixture.svelte.d.ts +9 -0
  59. package/dist/test-fixtures/TemplateFixture.svelte.d.ts.map +1 -0
  60. package/dist/test-fixtures/capture-sink.d.ts +10 -0
  61. package/dist/test-fixtures/capture-sink.d.ts.map +1 -0
  62. package/dist/test-fixtures/capture-sink.js +5 -0
  63. package/dist/types.d.ts +99 -0
  64. package/dist/types.d.ts.map +1 -0
  65. package/dist/types.js +26 -0
  66. package/dist/use-active-viewport.svelte.d.ts +14 -0
  67. package/dist/use-active-viewport.svelte.d.ts.map +1 -0
  68. package/dist/use-active-viewport.svelte.js +83 -0
  69. package/package.json +55 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ ## 0.3.1 (2026-07-17)
2
+
3
+ ### 🚀 Features
4
+
5
+ - design-token resolution via resolveToken + useDesignValues [AIS-149] ([#53](https://github.com/contentful/experiences/pull/53))
6
+
7
+ ### 🧱 Updated Dependencies
8
+
9
+ - Updated client to 0.1.1
10
+ - Updated design to 0.5.1
11
+ - Updated core to 0.5.1
12
+
13
+ ## 0.3.0 (2026-07-17)
14
+
15
+ ### 🚀 Features
16
+
17
+ - add packages/client with fetchExperience and delivery client [AIS-147] ([#30](https://github.com/contentful/experiences/pull/30), [#27](https://github.com/contentful/experiences/issues/27))
18
+
19
+ ### 🧱 Updated Dependencies
20
+
21
+ - Updated client to 0.1.0
22
+ - Updated design to 0.5.0
23
+ - Updated core to 0.5.0
24
+
25
+ ## 0.2.0 (2026-07-08)
26
+
27
+ ### 🚀 Features
28
+
29
+ - idiomatic adapters — bare components + context hooks ([0365bfb](https://github.com/contentful/experiences/commit/0365bfb))
30
+
31
+ ### 🧱 Updated Dependencies
32
+
33
+ - Updated design to 0.4.0
34
+ - Updated core to 0.4.0
35
+
36
+ ## 0.1.0 (2026-06-24)
37
+
38
+ ### 🚀 Features
39
+
40
+ - more-robust examples + simple/advanced README split + contentful prop ([#18](https://github.com/contentful/experiences/pull/18))
41
+
42
+ ### 🧱 Updated Dependencies
43
+
44
+ - Updated design to 0.3.0
45
+ - Updated core to 0.3.0
46
+
47
+ ## 0.0.1 (2026-06-24)
48
+
49
+ ### 🧱 Updated Dependencies
50
+
51
+ - Updated design to 0.2.0
52
+ - Updated core to 0.2.0
package/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # @contentful/experiences-svelte
2
+
3
+ > ⚠️ **Pre-alpha.** Not yet published to npm. APIs are unstable and will change.
4
+
5
+ The Svelte adapter for the Contentful Experiences SDK suite. Renders Experience payloads from the Experience Delivery API (XDA) using customer-supplied Svelte 5 components.
6
+
7
+ ```sh
8
+ npm install @contentful/experiences-svelte @contentful/experience-delivery
9
+ ```
10
+
11
+ This is the **only SDK package customers install** — it re-exports everything from `@contentful/experiences-core` and `@contentful/experiences-design` that consumers need. The other packages are workspace-internal.
12
+
13
+ The public API mirrors `@contentful/experiences-react` 1:1 in shape; only the rendering primitives change (`component: SvelteComponent` instead of `render: () => ReactNode`, Snippets instead of `children: ReactNode`).
14
+
15
+ ---
16
+
17
+ ## Public API
18
+
19
+ ### Authoring
20
+
21
+ ```ts
22
+ defineComponent<Props>(config); // Type-narrowing identity for component-type configs
23
+ defineTemplate<Props>(config); // Same shape, for page-level template wrappers
24
+ ```
25
+
26
+ ### Resolver
27
+
28
+ ```ts
29
+ resolveExperience(payload, config, opts?); // Async — walks payload, runs resolveData, returns a PortableRenderPlan
30
+ ```
31
+
32
+ ### Renderers
33
+
34
+ ```ts
35
+ ServerExperienceRenderer; // SSR-safe; active viewport seeded from initialViewportId
36
+ ClientExperienceRenderer; // Subscribes to window.matchMedia via runes
37
+ MissingComponent; // Default fallback for unregistered component types
38
+ useActiveViewport; // Rune-backed reactive object; rarely needed by consumers
39
+ ```
40
+
41
+ ### Re-exported types and utilities
42
+
43
+ ```ts
44
+ // From core
45
+ type Config, Components, Templates,
46
+ type ComponentConfig, TemplateConfig,
47
+ type ComponentProps, TemplateProps,
48
+ type RenderContext,
49
+ type ExperiencePayload, ExperienceNode, ComponentTypeNode, TemplateNode,
50
+ type PortableRenderPlan, PortableRenderNode, PortableTemplate,
51
+ type DesignPropValue, ManualDesignValue, DesignToken, ValuesByViewport,
52
+ type ViewportDef, ExperienceContext, ResolveContext,
53
+ type ResolverConfig, ResolveExperienceOptions
54
+
55
+ // From design (for customers who want to do their own viewport-aware resolution)
56
+ getValueForViewport, getViewportIndex, resolveDesignProperties, toCssMediaQuery
57
+ ```
58
+
59
+ ---
60
+
61
+ ## Quick reference
62
+
63
+ ```svelte
64
+ <!-- Button.svelte -->
65
+ <script lang="ts">
66
+ let { text = 'Button', url, type = 'primary' }: ButtonProps = $props();
67
+ </script>
68
+ {#if url}
69
+ <a href={url}>{text}</a>
70
+ {:else}
71
+ <button type="button">{text}</button>
72
+ {/if}
73
+ ```
74
+
75
+ ```ts
76
+ // experience-config.ts
77
+ import { defineComponent, type Config, type Components } from '@contentful/experiences-svelte';
78
+ import Button from './components/Button.svelte';
79
+ import type { ButtonProps } from './components/Button.svelte';
80
+
81
+ const components: Components = {
82
+ button: defineComponent<ButtonProps>({
83
+ defaults: { type: 'primary' },
84
+ resolveData: ({ content }) => ({ url: ensureScheme(content.url) }),
85
+ component: Button,
86
+ }),
87
+ };
88
+
89
+ export const experienceConfig: Config = { components };
90
+ ```
91
+
92
+ ```svelte
93
+ <!-- +page.svelte -->
94
+ <script lang="ts">
95
+ import { ServerExperienceRenderer } from '@contentful/experiences-svelte';
96
+ let { data } = $props();
97
+ </script>
98
+ <ServerExperienceRenderer experience={data.experience} config={experienceConfig} />
99
+ ```
100
+
101
+ For the full getting-started walkthrough, the merge-precedence rules, viewport handling, and design rationale, see the [root README](../../README.md) and [`AGENTS.md`](../../AGENTS.md).
102
+
103
+ ---
104
+
105
+ ## License
106
+
107
+ MIT. See the repository [`LICENSE`](../../LICENSE) and [`NOTICE`](../../NOTICE) for full attribution.
@@ -0,0 +1,81 @@
1
+ <!--
2
+ * Client Experience renderer. First paint matches the server renderer
3
+ * (active viewport resolved from `initialViewportId`); after hydration,
4
+ * `useActiveViewport` takes over via `window.matchMedia` and re-renders
5
+ * when the viewport changes.
6
+ *
7
+ * Safe to render on the server: `useActiveViewport` no-ops outside the
8
+ * browser, so SSR output matches `<ServerExperienceRenderer>` given the
9
+ * same seed.
10
+ -->
11
+ <script lang="ts">
12
+ import type { ExperienceContext, ViewportDef } from '@contentful/experiences-core';
13
+
14
+ import MissingComponent from './MissingComponent.svelte';
15
+ import NodesRenderer from './NodesRenderer.svelte';
16
+ import WrapWithTemplate from './WrapWithTemplate.svelte';
17
+ import type { ClientExperienceRendererProps } from './component-props.js';
18
+ import { setExperience } from './context.js';
19
+ import type { RenderContext } from './types.js';
20
+ import { useActiveViewport } from './use-active-viewport.svelte.js';
21
+
22
+ const DEFAULT_CONTEXT: ExperienceContext = {
23
+ isPreview: false,
24
+ metadata: {},
25
+ viewports: [],
26
+ };
27
+
28
+ const FALLBACK_VIEWPORT: ViewportDef = {
29
+ id: '_',
30
+ query: '*',
31
+ displayName: 'Default',
32
+ previewSize: '100%',
33
+ };
34
+
35
+ let {
36
+ experience,
37
+ config,
38
+ initialViewportId,
39
+ context,
40
+ renderUnknown = MissingComponent,
41
+ }: ClientExperienceRendererProps = $props();
42
+
43
+ const viewports = $derived(experience?.viewports ?? []);
44
+ const tracker = useActiveViewport(viewports, initialViewportId);
45
+
46
+ // A $state-backed mirror so descendants reading getExperience() stay
47
+ // reactive across viewport changes. The fields update in an $effect below.
48
+ const liveContext = $state<RenderContext>({
49
+ ...DEFAULT_CONTEXT,
50
+ ...context,
51
+ metadata: { ...DEFAULT_CONTEXT.metadata, ...(context?.metadata ?? {}) },
52
+ viewports: experience?.viewports ?? [],
53
+ activeViewport: experience?.viewports[0] ?? FALLBACK_VIEWPORT,
54
+ activeViewportIndex: 0,
55
+ });
56
+
57
+ setExperience(liveContext);
58
+
59
+ $effect(() => {
60
+ if (!experience) return;
61
+ const idx = tracker.activeViewportIndex;
62
+ liveContext.viewports = experience.viewports;
63
+ liveContext.activeViewport = experience.viewports[idx] ?? FALLBACK_VIEWPORT;
64
+ liveContext.activeViewportIndex = idx;
65
+ liveContext.isPreview = context?.isPreview ?? false;
66
+ liveContext.metadata = { ...DEFAULT_CONTEXT.metadata, ...(context?.metadata ?? {}) };
67
+ });
68
+ </script>
69
+
70
+ {#if experience}
71
+ <WrapWithTemplate template={experience.template} {config} experience={liveContext}>
72
+ {#snippet children()}
73
+ <NodesRenderer
74
+ nodes={experience.nodes}
75
+ {config}
76
+ experience={liveContext}
77
+ {renderUnknown}
78
+ />
79
+ {/snippet}
80
+ </WrapWithTemplate>
81
+ {/if}
@@ -0,0 +1,4 @@
1
+ declare const ClientExperienceRenderer: import("svelte").Component<import("./component-props.js").ServerExperienceRendererProps, {}, "">;
2
+ type ClientExperienceRenderer = ReturnType<typeof ClientExperienceRenderer>;
3
+ export default ClientExperienceRenderer;
4
+ //# sourceMappingURL=ClientExperienceRenderer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClientExperienceRenderer.svelte.d.ts","sourceRoot":"","sources":["../src/ClientExperienceRenderer.svelte.ts"],"names":[],"mappings":"AAmFA,QAAA,MAAM,wBAAwB,kGAAwC,CAAC;AACvE,KAAK,wBAAwB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC5E,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,35 @@
1
+ <!--
2
+ * Default fallback rendered when an instance references a component type
3
+ * not present in the Config.
4
+ *
5
+ * Behavior: a visible red error box in preview, silent null in production.
6
+ * Customers can override per-render via <ServerExperienceRenderer renderUnknown=... />.
7
+ -->
8
+ <script lang="ts">
9
+ import type { MissingComponentProps } from './component-props.js';
10
+ import { getExperience } from './context.js';
11
+
12
+ let { componentTypeId, nodeId }: MissingComponentProps = $props();
13
+ const experience = getExperience();
14
+
15
+ $effect(() => {
16
+ if (typeof console !== 'undefined') {
17
+ const idLabel = nodeId ? ` (nodeId: ${nodeId})` : '';
18
+ console.warn(
19
+ `[@contentful/experiences] No component registered for type "${componentTypeId}"${idLabel}.`
20
+ );
21
+ }
22
+ });
23
+ </script>
24
+
25
+ {#if experience.isPreview}
26
+ <div
27
+ style="border: 2px solid red; padding: 1rem; color: red; background: #fff;"
28
+ data-experiences-missing={componentTypeId}
29
+ >
30
+ <strong>Missing component &lsquo;{componentTypeId}&rsquo;</strong>
31
+ <p>
32
+ This component is referenced by the Experience payload but is not registered in the Config.
33
+ </p>
34
+ </div>
35
+ {/if}
@@ -0,0 +1,5 @@
1
+ import type { MissingComponentProps } from './component-props.js';
2
+ declare const MissingComponent: import("svelte").Component<MissingComponentProps, {}, "">;
3
+ type MissingComponent = ReturnType<typeof MissingComponent>;
4
+ export default MissingComponent;
5
+ //# sourceMappingURL=MissingComponent.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MissingComponent.svelte.d.ts","sourceRoot":"","sources":["../src/MissingComponent.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAgClE,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,83 @@
1
+ <!--
2
+ * Single-node renderer. Splits out of NodesRenderer so the per-node
3
+ * `setContext` for the contentful payload happens during *that node's*
4
+ * component init (Svelte's setContext can only be called at top level).
5
+ -->
6
+ <script lang="ts">
7
+ import type { Snippet } from 'svelte';
8
+
9
+ import type { PortableRenderNode } from '@contentful/experiences-core';
10
+ import { applyTokenResolver, resolveDesignProperties } from '@contentful/experiences-design';
11
+
12
+ import { setContentfulComponent, setResolvedDesign } from './context.js';
13
+ import type { RenderUnknown } from './component-props.js';
14
+ import {
15
+ normalizeComponentRegistration,
16
+ type ContentfulComponent,
17
+ type Config,
18
+ type RenderContext,
19
+ } from './types.js';
20
+
21
+ interface NodeRendererProps {
22
+ node: PortableRenderNode;
23
+ config: Config;
24
+ experience: RenderContext;
25
+ renderUnknown: RenderUnknown;
26
+ children: Snippet;
27
+ }
28
+
29
+ let { node, config, experience, renderUnknown, children }: NodeRendererProps = $props();
30
+
31
+ const entry = $derived(config.components[node.registration.componentTypeId]);
32
+ const componentConfig = $derived(entry ? normalizeComponentRegistration(entry) : null);
33
+
34
+ const contentful: ContentfulComponent = {
35
+ componentTypeId: node.registration.componentTypeId,
36
+ nodeId: node.nodeId,
37
+ content: node.props.content,
38
+ design: node.props.design,
39
+ resolved: node.props.resolved,
40
+ slots: node.slots,
41
+ };
42
+ setContentfulComponent(contentful);
43
+
44
+ // Cascade design + resolve tokens; published on context for getDesignValues(),
45
+ // never merged into props.
46
+ const tokenResolvedDesign = $derived.by(() => {
47
+ const resolvedDesign = resolveDesignProperties(
48
+ node.props.design,
49
+ experience.viewports,
50
+ experience.activeViewportIndex
51
+ );
52
+ const { props, unresolved } = applyTokenResolver(resolvedDesign, config.resolveToken);
53
+ if (unresolved.length && typeof console !== 'undefined') {
54
+ console.warn(
55
+ `[@contentful/experiences-svelte] resolveToken returned undefined for token id(s) on "${node.registration.componentTypeId}": ${unresolved.join(', ')}. getDesignValues() will omit those keys.`
56
+ );
57
+ }
58
+ return props;
59
+ });
60
+
61
+ setResolvedDesign(() => tokenResolvedDesign);
62
+
63
+ const composed = $derived.by(() => {
64
+ if (!componentConfig) return null;
65
+ return {
66
+ ...componentConfig.defaults,
67
+ ...node.props.content,
68
+ ...node.props.resolved,
69
+ children,
70
+ };
71
+ });
72
+ </script>
73
+
74
+ {#if componentConfig && composed}
75
+ {@const Cmp = componentConfig.component}
76
+ <Cmp {...composed} />
77
+ {:else}
78
+ {@const Unknown = renderUnknown}
79
+ <Unknown
80
+ componentTypeId={node.registration.componentTypeId}
81
+ nodeId={node.nodeId}
82
+ />
83
+ {/if}
@@ -0,0 +1,15 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { PortableRenderNode } from '@contentful/experiences-core';
3
+ import type { RenderUnknown } from './component-props.js';
4
+ import { type Config, type RenderContext } from './types.js';
5
+ interface NodeRendererProps {
6
+ node: PortableRenderNode;
7
+ config: Config;
8
+ experience: RenderContext;
9
+ renderUnknown: RenderUnknown;
10
+ children: Snippet;
11
+ }
12
+ declare const NodeRenderer: import("svelte").Component<NodeRendererProps, {}, "">;
13
+ type NodeRenderer = ReturnType<typeof NodeRenderer>;
14
+ export default NodeRenderer;
15
+ //# sourceMappingURL=NodeRenderer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodeRenderer.svelte.d.ts","sourceRoot":"","sources":["../src/NodeRenderer.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAIvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAGH,KAAK,MAAM,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAGpB,UAAU,iBAAiB;IACzB,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,aAAa,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAoEH,QAAA,MAAM,YAAY,uDAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
@@ -0,0 +1,51 @@
1
+ <!--
2
+ * Recursive renderer over PortableRenderNodes. Customer components receive
3
+ * the merged content / design / resolveData prop bag plus the default
4
+ * `children` slot as a named Snippet:
5
+ *
6
+ * <script lang="ts">
7
+ * import type { Snippet } from 'svelte';
8
+ * let { text, children }: { text?: string; children?: Snippet } = $props();
9
+ * </script>
10
+ * <button>
11
+ * {text}
12
+ * {@render children?.()}
13
+ * </button>
14
+ *
15
+ * Only the `children` slot is rendered automatically — Svelte 5 Snippets are
16
+ * compile-time entities, so the renderer can't synthesize an unbounded set
17
+ * of named Snippet props from runtime payload data. Additional named slots
18
+ * are reachable through `getContentfulComponent().slots` and can be rendered
19
+ * with the exported `<NodesRenderer />` (see README).
20
+ *
21
+ * The Experience runtime context and the raw Contentful payload are read
22
+ * via `getExperience` / `getContentfulComponent`.
23
+ *
24
+ * Server vs client variants share this component; they differ only in how
25
+ * the active viewport is sourced (initial seed vs reactive matchMedia).
26
+ -->
27
+ <script lang="ts">
28
+ import type { PortableRenderNode } from '@contentful/experiences-core';
29
+
30
+ import NodeRenderer from './NodeRenderer.svelte';
31
+ import NodesRenderer from './NodesRenderer.svelte';
32
+ import type { RenderUnknown } from './component-props.js';
33
+ import type { Config, RenderContext } from './types.js';
34
+
35
+ interface NodesRendererProps {
36
+ nodes: PortableRenderNode[];
37
+ config: Config;
38
+ experience: RenderContext;
39
+ renderUnknown: RenderUnknown;
40
+ }
41
+
42
+ let { nodes, config, experience, renderUnknown }: NodesRendererProps = $props();
43
+ </script>
44
+
45
+ {#each nodes as node, index (node.nodeId ?? index)}
46
+ {#snippet children()}
47
+ {@const childrenNodes = (node.slots.children ?? []) as PortableRenderNode[]}
48
+ <NodesRenderer nodes={childrenNodes} {config} {experience} {renderUnknown} />
49
+ {/snippet}
50
+ <NodeRenderer {node} {config} {experience} {renderUnknown} {children} />
51
+ {/each}
@@ -0,0 +1,14 @@
1
+ import type { PortableRenderNode } from '@contentful/experiences-core';
2
+ import NodesRenderer from './NodesRenderer.svelte';
3
+ import type { RenderUnknown } from './component-props.js';
4
+ import type { Config, RenderContext } from './types.js';
5
+ interface NodesRendererProps {
6
+ nodes: PortableRenderNode[];
7
+ config: Config;
8
+ experience: RenderContext;
9
+ renderUnknown: RenderUnknown;
10
+ }
11
+ declare const NodesRenderer: import("svelte").Component<NodesRendererProps, {}, "">;
12
+ type NodesRenderer = ReturnType<typeof NodesRenderer>;
13
+ export default NodesRenderer;
14
+ //# sourceMappingURL=NodesRenderer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NodesRenderer.svelte.d.ts","sourceRoot":"","sources":["../src/NodesRenderer.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,aAAa,MAAM,wBAAwB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGtD,UAAU,kBAAkB;IAC1B,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,aAAa,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;CAC9B;AAwBH,QAAA,MAAM,aAAa,wDAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,70 @@
1
+ <!--
2
+ * Server-safe Experience renderer. Resolves the active viewport once from
3
+ * `initialViewportId` (typically derived from User-Agent on the request)
4
+ * and renders without any reactive subscription. SSR-friendly.
5
+ *
6
+ * SSR + interactive editor mode are mutually exclusive — the message-event
7
+ * preview client requires window listeners and lives only in the client
8
+ * renderer. For editor mode, render the client variant on a hydrated route.
9
+ -->
10
+ <script lang="ts">
11
+ import type { ExperienceContext, ViewportDef } from '@contentful/experiences-core';
12
+ import { getViewportIndex } from '@contentful/experiences-design';
13
+
14
+ import MissingComponent from './MissingComponent.svelte';
15
+ import NodesRenderer from './NodesRenderer.svelte';
16
+ import WrapWithTemplate from './WrapWithTemplate.svelte';
17
+ import type { ServerExperienceRendererProps } from './component-props.js';
18
+ import { setExperience } from './context.js';
19
+ import type { RenderContext } from './types.js';
20
+
21
+ const DEFAULT_CONTEXT: ExperienceContext = {
22
+ isPreview: false,
23
+ metadata: {},
24
+ viewports: [],
25
+ };
26
+
27
+ const FALLBACK_VIEWPORT: ViewportDef = {
28
+ id: '_',
29
+ query: '*',
30
+ displayName: 'Default',
31
+ previewSize: '100%',
32
+ };
33
+
34
+ let {
35
+ experience,
36
+ config,
37
+ initialViewportId,
38
+ context,
39
+ renderUnknown = MissingComponent,
40
+ }: ServerExperienceRendererProps = $props();
41
+
42
+ function buildContext(): RenderContext {
43
+ const viewports = experience?.viewports ?? [];
44
+ const idx = experience ? getViewportIndex(experience.viewports, initialViewportId) : 0;
45
+ return {
46
+ ...DEFAULT_CONTEXT,
47
+ ...context,
48
+ metadata: { ...DEFAULT_CONTEXT.metadata, ...(context?.metadata ?? {}) },
49
+ viewports,
50
+ activeViewport: experience?.viewports[idx] ?? FALLBACK_VIEWPORT,
51
+ activeViewportIndex: idx,
52
+ };
53
+ }
54
+
55
+ const renderContext = buildContext();
56
+ setExperience(renderContext);
57
+ </script>
58
+
59
+ {#if experience}
60
+ <WrapWithTemplate template={experience.template} {config} experience={renderContext}>
61
+ {#snippet children()}
62
+ <NodesRenderer
63
+ nodes={experience.nodes}
64
+ {config}
65
+ experience={renderContext}
66
+ {renderUnknown}
67
+ />
68
+ {/snippet}
69
+ </WrapWithTemplate>
70
+ {/if}
@@ -0,0 +1,5 @@
1
+ import type { ServerExperienceRendererProps } from './component-props.js';
2
+ declare const ServerExperienceRenderer: import("svelte").Component<ServerExperienceRendererProps, {}, "">;
3
+ type ServerExperienceRenderer = ReturnType<typeof ServerExperienceRenderer>;
4
+ export default ServerExperienceRenderer;
5
+ //# sourceMappingURL=ServerExperienceRenderer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ServerExperienceRenderer.svelte.d.ts","sourceRoot":"","sources":["../src/ServerExperienceRenderer.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,sBAAsB,CAAC;AAgE1E,QAAA,MAAM,wBAAwB,mEAAwC,CAAC;AACvE,KAAK,wBAAwB,GAAG,UAAU,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC5E,eAAe,wBAAwB,CAAC"}
@@ -0,0 +1,87 @@
1
+ <!--
2
+ * Wraps the rendered Experience nodes with the page-level template when the
3
+ * plan carries one and the customer registered a template config under that
4
+ * id. When the template is referenced but unregistered, warns once and
5
+ * renders children unwrapped — graceful degradation matches the
6
+ * unknown-component fallback story.
7
+ -->
8
+ <script lang="ts">
9
+ import type { Snippet } from 'svelte';
10
+
11
+ import type { PortableTemplate } from '@contentful/experiences-core';
12
+ import { applyTokenResolver, resolveDesignProperties } from '@contentful/experiences-design';
13
+
14
+ import { setContentfulTemplate, setResolvedDesign } from './context.js';
15
+ import {
16
+ normalizeTemplateRegistration,
17
+ type ContentfulTemplate,
18
+ type Config,
19
+ type RenderContext,
20
+ } from './types.js';
21
+
22
+ interface WrapWithTemplateProps {
23
+ template: PortableTemplate | undefined;
24
+ config: Config;
25
+ experience: RenderContext;
26
+ children: Snippet;
27
+ }
28
+
29
+ let { template, config, experience, children }: WrapWithTemplateProps = $props();
30
+
31
+ // setContext must run during synchronous component init — call once with
32
+ // the initial template value (templates don't toggle on/off mid-mount).
33
+ if (template) {
34
+ setContentfulTemplate({
35
+ templateId: template.templateId,
36
+ content: template.props.content,
37
+ design: template.props.design,
38
+ resolved: template.props.resolved,
39
+ } satisfies ContentfulTemplate);
40
+ }
41
+
42
+ const entry = $derived(template ? config.templates?.[template.templateId] : undefined);
43
+ const templateConfig = $derived(entry ? normalizeTemplateRegistration(entry) : null);
44
+
45
+ const tokenResolvedDesign = $derived.by(() => {
46
+ if (!template) return {};
47
+ const resolvedDesign = resolveDesignProperties(
48
+ template.props.design,
49
+ experience.viewports,
50
+ experience.activeViewportIndex
51
+ );
52
+ const { props, unresolved } = applyTokenResolver(resolvedDesign, config.resolveToken);
53
+ if (unresolved.length && typeof console !== 'undefined') {
54
+ console.warn(
55
+ `[@contentful/experiences-svelte] resolveToken returned undefined for token id(s) on template "${template.templateId}": ${unresolved.join(', ')}. getDesignValues() will omit those keys.`
56
+ );
57
+ }
58
+ return props;
59
+ });
60
+
61
+ // A getter (not a snapshot) so getDesignValues() stays reactive; not merged into props.
62
+ setResolvedDesign(() => tokenResolvedDesign);
63
+
64
+ const composed = $derived.by(() => {
65
+ if (!template || !templateConfig) return null;
66
+ return {
67
+ ...templateConfig.defaults,
68
+ ...template.props.content,
69
+ ...template.props.resolved,
70
+ };
71
+ });
72
+
73
+ $effect(() => {
74
+ if (template && !templateConfig && typeof console !== 'undefined') {
75
+ console.warn(
76
+ `[@contentful/experiences-svelte] No template registered for id "${template.templateId}". Rendering nodes without the template wrapper.`
77
+ );
78
+ }
79
+ });
80
+ </script>
81
+
82
+ {#if template && templateConfig && composed}
83
+ {@const Tpl = templateConfig.component}
84
+ <Tpl {...composed} {children} />
85
+ {:else}
86
+ {@render children()}
87
+ {/if}
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { PortableTemplate } from '@contentful/experiences-core';
3
+ import { type Config, type RenderContext } from './types.js';
4
+ interface WrapWithTemplateProps {
5
+ template: PortableTemplate | undefined;
6
+ config: Config;
7
+ experience: RenderContext;
8
+ children: Snippet;
9
+ }
10
+ declare const WrapWithTemplate: import("svelte").Component<WrapWithTemplateProps, {}, "">;
11
+ type WrapWithTemplate = ReturnType<typeof WrapWithTemplate>;
12
+ export default WrapWithTemplate;
13
+ //# sourceMappingURL=WrapWithTemplate.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WrapWithTemplate.svelte.d.ts","sourceRoot":"","sources":["../src/WrapWithTemplate.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAIrE,OAAO,EAGH,KAAK,MAAM,EACX,KAAK,aAAa,EACnB,MAAM,YAAY,CAAC;AAGpB,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,aAAa,CAAC;IAC1B,QAAQ,EAAE,OAAO,CAAC;CACnB;AA0EH,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}