@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.
- package/CHANGELOG.md +52 -0
- package/README.md +107 -0
- package/dist/ClientExperienceRenderer.svelte +81 -0
- package/dist/ClientExperienceRenderer.svelte.d.ts +4 -0
- package/dist/ClientExperienceRenderer.svelte.d.ts.map +1 -0
- package/dist/MissingComponent.svelte +35 -0
- package/dist/MissingComponent.svelte.d.ts +5 -0
- package/dist/MissingComponent.svelte.d.ts.map +1 -0
- package/dist/NodeRenderer.svelte +83 -0
- package/dist/NodeRenderer.svelte.d.ts +15 -0
- package/dist/NodeRenderer.svelte.d.ts.map +1 -0
- package/dist/NodesRenderer.svelte +51 -0
- package/dist/NodesRenderer.svelte.d.ts +14 -0
- package/dist/NodesRenderer.svelte.d.ts.map +1 -0
- package/dist/ServerExperienceRenderer.svelte +70 -0
- package/dist/ServerExperienceRenderer.svelte.d.ts +5 -0
- package/dist/ServerExperienceRenderer.svelte.d.ts.map +1 -0
- package/dist/WrapWithTemplate.svelte +87 -0
- package/dist/WrapWithTemplate.svelte.d.ts +13 -0
- package/dist/WrapWithTemplate.svelte.d.ts.map +1 -0
- package/dist/component-props.d.ts +18 -0
- package/dist/component-props.d.ts.map +1 -0
- package/dist/component-props.js +13 -0
- package/dist/context.d.ts +20 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +61 -0
- package/dist/design-utils.d.ts +16 -0
- package/dist/design-utils.d.ts.map +1 -0
- package/dist/design-utils.js +27 -0
- package/dist/get-design-values.d.ts +14 -0
- package/dist/get-design-values.d.ts.map +1 -0
- package/dist/get-design-values.js +16 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/server-renderer.test.d.ts +2 -0
- package/dist/server-renderer.test.d.ts.map +1 -0
- package/dist/server-renderer.test.js +566 -0
- package/dist/test-fixtures/ButtonFixture.svelte +8 -0
- package/dist/test-fixtures/ButtonFixture.svelte.d.ts +7 -0
- package/dist/test-fixtures/ButtonFixture.svelte.d.ts.map +1 -0
- package/dist/test-fixtures/CapturingComponent.svelte +18 -0
- package/dist/test-fixtures/CapturingComponent.svelte.d.ts +4 -0
- package/dist/test-fixtures/CapturingComponent.svelte.d.ts.map +1 -0
- package/dist/test-fixtures/ContainerFixture.svelte +12 -0
- package/dist/test-fixtures/ContainerFixture.svelte.d.ts +8 -0
- package/dist/test-fixtures/ContainerFixture.svelte.d.ts.map +1 -0
- package/dist/test-fixtures/HeadingFixture.svelte +8 -0
- package/dist/test-fixtures/HeadingFixture.svelte.d.ts +7 -0
- package/dist/test-fixtures/HeadingFixture.svelte.d.ts.map +1 -0
- package/dist/test-fixtures/ItemFixture.svelte +11 -0
- package/dist/test-fixtures/ItemFixture.svelte.d.ts +8 -0
- package/dist/test-fixtures/ItemFixture.svelte.d.ts.map +1 -0
- package/dist/test-fixtures/PrecedenceFixture.svelte +5 -0
- package/dist/test-fixtures/PrecedenceFixture.svelte.d.ts +7 -0
- package/dist/test-fixtures/PrecedenceFixture.svelte.d.ts.map +1 -0
- package/dist/test-fixtures/TemplateFixture.svelte +18 -0
- package/dist/test-fixtures/TemplateFixture.svelte.d.ts +9 -0
- package/dist/test-fixtures/TemplateFixture.svelte.d.ts.map +1 -0
- package/dist/test-fixtures/capture-sink.d.ts +10 -0
- package/dist/test-fixtures/capture-sink.d.ts.map +1 -0
- package/dist/test-fixtures/capture-sink.js +5 -0
- package/dist/types.d.ts +99 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +26 -0
- package/dist/use-active-viewport.svelte.d.ts +14 -0
- package/dist/use-active-viewport.svelte.d.ts.map +1 -0
- package/dist/use-active-viewport.svelte.js +83 -0
- 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 ‘{componentTypeId}’</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"}
|