@contentful/experiences-react 0.12.0 → 0.13.0

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 CHANGED
@@ -1,3 +1,144 @@
1
+ ## 0.13.0 (2026-10-08)
2
+
3
+ ### 🚀 Features
4
+
5
+ - ⚠️ remove sdk viewport surface [SPA-5275] ([#220](https://github.com/contentful/experiences/pull/220))
6
+
7
+ ### ⚠️ Breaking Changes
8
+
9
+ - remove sdk viewport surface [SPA-5275] ([#220](https://github.com/contentful/experiences/pull/220))
10
+ The SDK no longer exposes viewport definitions, viewport-aware design-value resolution, or active-viewport hooks. Use CSS media queries or application-owned matchMedia integration for responsive behavior. Delivered design property values are now flat and require no migration."
11
+ M AGENTS.md
12
+ M ARCHITECTURE.md
13
+ M CONTRIBUTING.md
14
+ M README.md
15
+ M catalog-info.yaml
16
+ M examples/angular/README.md
17
+ M examples/angular/src/app/components/heading.component.ts
18
+ D examples/angular/src/app/lib/detect-viewport.ts
19
+ M examples/angular/src/app/lib/experience-config.ts
20
+ M examples/angular/src/app/lib/experience-route-data.ts
21
+ M examples/angular/src/app/pages/experience-page.component.ts
22
+ M examples/angular/src/server.ts
23
+ M examples/nextjs/README.md
24
+ M examples/nextjs/app/[slug]/page.tsx
25
+ M examples/nextjs/components/Heading.tsx
26
+ M examples/nextjs/components/LivePreviewExperience.tsx
27
+ M examples/nextjs/components/Section.tsx
28
+ D examples/nextjs/lib/detect-viewport.ts
29
+ M examples/nextjs/lib/experience-config.tsx
30
+ M examples/scripts/bootstrap-example.ts
31
+ M examples/scripts/fixture/experience.ts
32
+ M examples/scripts/fixture/types.ts
33
+ M examples/sveltekit/README.md
34
+ M examples/sveltekit/src/lib/components/Heading.svelte
35
+ D examples/sveltekit/src/lib/detect-viewport.ts
36
+ M examples/sveltekit/src/lib/experience-config.ts
37
+ M examples/sveltekit/src/routes/[slug]/+page.server.ts
38
+ M examples/sveltekit/src/routes/[slug]/+page.svelte
39
+ M examples/sveltekit/vite.config.ts
40
+ M package-lock.json
41
+ M packages/adapter-angular/README.md
42
+ M packages/adapter-angular/src/client-experience-renderer.component.ts
43
+ M packages/adapter-angular/src/context.ts
44
+ M packages/adapter-angular/src/debug-experience.test.ts
45
+ M packages/adapter-angular/src/debug-panel-coverage.ssr.test.ts
46
+ M packages/adapter-angular/src/debug-panel-coverage.test.ts
47
+ M packages/adapter-angular/src/dom-parity.test.ts
48
+ M packages/adapter-angular/src/experience-defaults.ts
49
+ M packages/adapter-angular/src/experience-scope.ts
50
+ M packages/adapter-angular/src/index.ts
51
+ D packages/adapter-angular/src/inject-active-viewport.ts
52
+ M packages/adapter-angular/src/inject-design-values.ts
53
+ M packages/adapter-angular/src/inject-experience-plan.ts
54
+ M packages/adapter-angular/src/live-preview-experience.ssr.test.ts
55
+ M packages/adapter-angular/src/live-preview-experience.test.ts
56
+ M packages/adapter-angular/src/node-render-engine.test.ts
57
+ M packages/adapter-angular/src/node-render-engine.ts
58
+ M packages/adapter-angular/src/node-scopes.ts
59
+ M packages/adapter-angular/src/nodes-renderer.ssr.test.ts
60
+ M packages/adapter-angular/src/server-experience-renderer.component.ts
61
+ M packages/adapter-angular/src/server-renderer.test.ts
62
+ M packages/adapter-angular/src/test-fixtures/render-harness.ts
63
+ M packages/adapter-angular/src/types.ts
64
+ M packages/adapter-react/README.md
65
+ M packages/adapter-react/src/client-renderer.tsx
66
+ M packages/adapter-react/src/context.tsx
67
+ M packages/adapter-react/src/debug-experience.test.tsx
68
+ M packages/adapter-react/src/debug-experience.tsx
69
+ M packages/adapter-react/src/debug-panel-coverage.test.tsx
70
+ M packages/adapter-react/src/index.ts
71
+ M packages/adapter-react/src/nodes-renderer.ssr.test.tsx
72
+ M packages/adapter-react/src/nodes-renderer.test.tsx
73
+ M packages/adapter-react/src/nodes-renderer.tsx
74
+ M packages/adapter-react/src/server-renderer.test.tsx
75
+ M packages/adapter-react/src/server-renderer.tsx
76
+ M packages/adapter-react/src/types.ts
77
+ D packages/adapter-react/src/use-active-viewport.ts
78
+ M packages/adapter-react/src/use-design-values.tsx
79
+ M packages/adapter-react/src/use-experience-plan.test.tsx
80
+ M packages/adapter-react/src/use-experience-plan.ts
81
+ M packages/adapter-react/src/use-live-preview-experience.test.tsx
82
+ M packages/adapter-react/src/use-live-preview.test.tsx
83
+ M packages/adapter-svelte/README.md
84
+ M packages/adapter-svelte/src/ClientExperienceRenderer.svelte
85
+ M packages/adapter-svelte/src/DebugExperience.svelte
86
+ M packages/adapter-svelte/src/DebugExperience.test.ts
87
+ M packages/adapter-svelte/src/NodeRenderer.svelte
88
+ M packages/adapter-svelte/src/NodeRenderer.test.ts
89
+ M packages/adapter-svelte/src/NodesRenderer.svelte
90
+ M packages/adapter-svelte/src/ServerExperienceRenderer.svelte
91
+ M packages/adapter-svelte/src/component-props.ts
92
+ M packages/adapter-svelte/src/context.ts
93
+ M packages/adapter-svelte/src/debug-panel-coverage.ssr.test.ts
94
+ M packages/adapter-svelte/src/debug-panel-coverage.test.ts
95
+ M packages/adapter-svelte/src/get-design-values.ts
96
+ M packages/adapter-svelte/src/index.ts
97
+ M packages/adapter-svelte/src/nodes-renderer.ssr.test.ts
98
+ M packages/adapter-svelte/src/server-renderer.test.ts
99
+ M packages/adapter-svelte/src/types.ts
100
+ D packages/adapter-svelte/src/use-active-viewport.svelte.ts
101
+ M packages/adapter-svelte/src/use-experience-plan.test.ts
102
+ M packages/adapter-svelte/src/use-live-preview-experience.test.ts
103
+ M packages/adapter-svelte/src/use-live-preview.test.ts
104
+ M packages/client/src/contentful-experiences.test.ts
105
+ M packages/client/src/contentful-experiences.ts
106
+ M packages/client/src/fetch-experience.test.ts
107
+ M packages/client/src/fetch-experience.ts
108
+ M packages/core/README.md
109
+ A packages/core/src/design-properties.test.ts
110
+ A packages/core/src/design-properties.ts
111
+ M packages/core/src/index.ts
112
+ M packages/core/src/resolve-experience.test.ts
113
+ M packages/core/src/resolve-experience.ts
114
+ M packages/core/src/types.ts
115
+ D packages/core/src/viewport.test.ts
116
+ D packages/core/src/viewport.ts
117
+ M packages/design/README.md
118
+ M packages/design/package.json
119
+ M packages/design/src/index.ts
120
+ D packages/design/src/media-query-matchers.ts
121
+ D packages/design/src/select-resolved-design.ts
122
+ D packages/design/src/viewport.test.ts
123
+ D packages/design/src/viewport.ts
124
+ M packages/live-preview/README.md
125
+ M packages/live-preview/src/experience-payload.ts
126
+ M packages/live-preview/src/fetch-preview-session.test.ts
127
+ M packages/live-preview/src/fetch-preview-session.ts
128
+ M packages/live-preview/src/live-preview-client.test.ts
129
+ M packages/node/README.md
130
+ M packages/node/src/contentful-experiences.test.ts
131
+ M packages/node/src/contentful-experiences.ts
132
+ M packages/web/README.md
133
+ M packages/web/src/contentful-experiences.test.ts
134
+
135
+ ### 🧱 Updated Dependencies
136
+
137
+ - Updated live-preview to 0.6.0
138
+ - Updated client to 0.9.0
139
+ - Updated design to 0.12.0
140
+ - Updated core to 0.12.0
141
+
1
142
  ## 0.12.0 (2026-10-06)
2
143
 
3
144
  ### 🧱 Updated Dependencies
package/README.md CHANGED
@@ -109,10 +109,9 @@ fails. The last valid `data` remains available.
109
109
  ### Renderers
110
110
 
111
111
  ```ts
112
- ServerExperienceRenderer; // RSC-friendly, active viewport seeded from initialViewportId
113
- ClientExperienceRenderer; // 'use client', subscribes to window.matchMedia, alias: ExperienceRenderer
112
+ ServerExperienceRenderer; // RSC-friendly renderer
113
+ ClientExperienceRenderer; // Client renderer, alias: ExperienceRenderer
114
114
  MissingComponent; // Default fallback for unregistered component types
115
- useActiveViewport; // Hook used inside ClientExperienceRenderer (you'll rarely need it directly)
116
115
  ```
117
116
 
118
117
  ### Styling + runtime context (hooks)
@@ -120,13 +119,13 @@ useActiveViewport; // Hook used inside ClientExperienceRenderer (you'll rarely n
120
119
  ```ts
121
120
  useDesignValues<T>(); // Escape hatch: the same resolved design record that auto-fills props
122
121
  toCss(design, options?); // Turns a design record into CSSProperties, keeping only real CSS keys
123
- useExperience(); // RenderContext: debug, metadata, viewports, activeViewport
122
+ useExperience(); // RenderContext: debug, metadata
124
123
  useContentfulComponent(); // Raw payload for the enclosing node (or null)
125
124
  useContentfulExperienceTemplate(); // Same, for an enclosing coded Experience Template node
126
125
  type ToCssOptions;
127
126
  ```
128
127
 
129
- Resolved design values (viewport-cascaded + token-resolved server-side) are **auto-filled onto your component's props** by key, alongside content. Styling from those props is the one recommended path. `useDesignValues()` exposes the same record as an escape hatch. Reach for it only for a nested child that isn't itself a registered component, or for design needed outside the render path (an effect, an imperative measurement) — see [Styling components](../../README.md#styling-components). Token resolution is configured with `resolveToken` on your `Config` (`type ResolveToken`).
128
+ Resolved design values are **auto-filled onto your component's props** by key, alongside content. Styling from those props is the one recommended path. `useDesignValues()` exposes the same record as an escape hatch. Reach for it only for a nested child that isn't itself a registered component, or for design needed outside the render path (an effect, an imperative measurement) — see [Styling components](../../README.md#styling-components). Token resolution is configured with `resolveToken` on your `Config` (`type ResolveToken`).
130
129
 
131
130
  ### Re-exported types and utilities
132
131
 
@@ -138,12 +137,10 @@ type ContentfulComponent, ContentfulExperienceTemplate,
138
137
  type RenderContext, ResolveToken,
139
138
  type ExperiencePayload, ExperienceNode, ComponentNode, ExperienceTemplateNode,
140
139
  type PortableRenderPlan, PortableRenderNode, PortableRegistration,
141
- type DesignPropValue, ManualDesignValue, DesignToken, ValuesByViewport,
142
- type ViewportDef, ExperienceContext, ResolveContext,
140
+ type DesignPropValue, ManualDesignValue, DesignToken, ExperienceContext, ResolveContext,
143
141
  type ResolverConfig, ResolveExperienceOptions
144
142
 
145
- // From design (if you want to do your own viewport-aware resolution)
146
- getValueForViewport, getViewportIndex, resolveDesignProperties, toCssMediaQuery,
143
+ // From design
147
144
  isCssProperty, toCssKey, CSS_PROPERTIES
148
145
  ```
149
146
 
@@ -232,7 +229,7 @@ export function Section({ children }: { children?: ReactNode[] }) {
232
229
 
233
230
  `children` is not special — it is simply the conventional name for the default slot. **Every** slot is merged into props under its own name, each as its own `ReactNode[]`, so a component with a `header` slot just declares `header?: ReactNode[]` and renders it the same way. This applies identically to coded Experience Templates: a template with a `content` slot receives a `content` prop.
234
231
 
235
- 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).
232
+ For the full getting-started walkthrough, merge-precedence rules, and design rationale, see the [root README](../../README.md) and [`AGENTS.md`](../../AGENTS.md).
236
233
 
237
234
  ---
238
235
 
@@ -8,11 +8,6 @@ import './missing-component.js';
8
8
  interface ClientExperienceRendererProps {
9
9
  experience: PortableRenderPlan | null | undefined;
10
10
  config: Config;
11
- /**
12
- * Viewport to render for, typically derived from the request's User-Agent.
13
- * Defaults to the viewport the plan was pre-resolved against.
14
- */
15
- initialViewportId?: string;
16
11
  /** Shallow-merges over the plan's `metadata`. Only needed to override it. */
17
12
  metadata?: Record<string, unknown>;
18
13
  /**
@@ -25,6 +20,6 @@ interface ClientExperienceRendererProps {
25
20
  /** Override the fallback rendered when a registered component throws. */
26
21
  renderError?: RenderError;
27
22
  }
28
- declare function ClientExperienceRenderer({ experience, config, initialViewportId, metadata, debug, renderUnknown, renderError, }: ClientExperienceRendererProps): ReactNode;
23
+ declare function ClientExperienceRenderer({ experience, config, metadata, debug, renderUnknown, renderError, }: ClientExperienceRendererProps): ReactNode;
29
24
 
30
25
  export { ClientExperienceRenderer, type ClientExperienceRendererProps };
@@ -7,22 +7,13 @@ import { DebugExperience } from "./debug-experience";
7
7
  import { ExperienceProvider } from "./context";
8
8
  import { MissingComponent } from "./missing-component";
9
9
  import { NodesRenderer } from "./nodes-renderer";
10
- import { useActiveViewport } from "./use-active-viewport";
11
10
  const DEFAULT_CONTEXT = {
12
11
  debug: false,
13
- metadata: {},
14
- viewports: []
15
- };
16
- const FALLBACK_VIEWPORT = {
17
- id: "_",
18
- query: "*",
19
- displayName: "Default",
20
- previewSize: "100%"
12
+ metadata: {}
21
13
  };
22
14
  function ClientExperienceRenderer({
23
15
  experience,
24
16
  config,
25
- initialViewportId,
26
17
  metadata,
27
18
  debug,
28
19
  renderUnknown = MissingComponent,
@@ -36,20 +27,12 @@ function ClientExperienceRenderer({
36
27
  );
37
28
  });
38
29
  }, []);
39
- const seedViewportId = initialViewportId ?? (experience ? experience.viewports[experience.fallbackViewportIndex]?.id : void 0);
40
- const { activeViewportIndex } = useActiveViewport(experience?.viewports ?? [], seedViewportId);
41
30
  if (!experience) return null;
42
31
  const resolvedDebug = debug ?? experience.debug;
43
- const contextViewports = experience.viewports.map((v) => ({ ...v }));
44
- const activeViewport = { ...experience.viewports[activeViewportIndex] ?? FALLBACK_VIEWPORT };
45
32
  const renderContext = {
46
33
  ...DEFAULT_CONTEXT,
47
34
  debug: resolvedDebug,
48
- metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...metadata ?? {} },
49
- viewports: contextViewports,
50
- activeViewport,
51
- activeViewportIndex,
52
- fallbackViewportIndex: experience.fallbackViewportIndex
35
+ metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...metadata ?? {} }
53
36
  };
54
37
  return /* @__PURE__ */ jsxs(ExperienceProvider, { value: renderContext, children: [
55
38
  /* @__PURE__ */ jsx(DiagnosticReporterContext.Provider, { value: onDiagnostic, children: /* @__PURE__ */ jsx(
@@ -57,9 +40,6 @@ function ClientExperienceRenderer({
57
40
  {
58
41
  nodes: experience.nodes,
59
42
  config,
60
- viewports: experience.viewports,
61
- activeViewportIndex,
62
- fallbackViewportIndex: experience.fallbackViewportIndex,
63
43
  renderUnknown,
64
44
  renderError,
65
45
  onDiagnostic
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client-renderer.tsx"],"sourcesContent":["/*\n * Client Experience renderer. First paint matches the server renderer; after\n * hydration `useActiveViewport` takes over via `matchMedia`. Server-safe: the\n * hook returns the seeded index and registers no listeners when there's no\n * window, so SSR output matches `<ServerExperienceRenderer>`.\n */\n\n'use client';\n\nimport { useCallback, useState, type ReactNode } from 'react';\n\nimport type {\n ExperienceContext,\n PortableRenderPlan,\n ViewportDef,\n} from '@contentful/experiences-sdk-core';\n\nimport { ComponentError } from './component-error';\nimport { DiagnosticReporterContext } from './component-error-boundary';\nimport { DebugExperience } from './debug-experience';\nimport { ExperienceProvider } from './context';\nimport { MissingComponent } from './missing-component';\nimport { NodesRenderer, type RenderError, type RenderUnknown } from './nodes-renderer';\nimport type { Config, RenderContext } from './types';\nimport { useActiveViewport } from './use-active-viewport';\n\nconst DEFAULT_CONTEXT: ExperienceContext = {\n debug: false,\n metadata: {},\n viewports: [],\n};\n\nconst FALLBACK_VIEWPORT: ViewportDef = {\n id: '_',\n query: '*',\n displayName: 'Default',\n previewSize: '100%',\n};\n\nexport interface ClientExperienceRendererProps {\n experience: PortableRenderPlan | null | undefined;\n config: Config;\n /**\n * Viewport to render for, typically derived from the request's User-Agent.\n * Defaults to the viewport the plan was pre-resolved against.\n */\n initialViewportId?: string;\n /** Shallow-merges over the plan's `metadata`. Only needed to override it. */\n metadata?: Record<string, unknown>;\n /**\n * Observability switch. When on: renders the visible missing-component box,\n * turns the default `renderUnknown` fallback into the debug component, and\n * auto-mounts `<DebugExperience>` after the tree. Defaults to the plan's `debug`.\n */\n debug?: boolean;\n renderUnknown?: RenderUnknown;\n /** Override the fallback rendered when a registered component throws. */\n renderError?: RenderError;\n}\n\nexport function ClientExperienceRenderer({\n experience,\n config,\n initialViewportId,\n metadata,\n debug,\n renderUnknown = MissingComponent,\n renderError = ComponentError,\n}: ClientExperienceRendererProps): ReactNode {\n const [renderDiagnostics, setRenderDiagnostics] = useState<Error[]>([]);\n // A `setState` updater rather than the SSR renderer's plain-array push: this\n // tree stays mounted and interactive, so a component that throws well after\n // first paint (a later re-render, an event handler) must still make\n // `<DebugExperience>` re-render with the new diagnostic — a mutated array\n // wouldn't trigger that.\n //\n // `queueMicrotask` defers the actual `setState` call: most of these\n // diagnostics (component-not-registered, malformed-slot,\n // experience-template-not-registered) are reported synchronously from\n // *inside* NodeRenderer's own render body, i.e. while React is mid-render\n // of a descendant — calling this component's setState right there is\n // exactly React's \"Cannot update a component while rendering a different\n // component\" anti-pattern. Escaping to a microtask (same \"break the\n // synchronous call chain\" rationale as core's resolveData deferral)\n // schedules the update for after the current render/commit fully unwinds.\n // `component-render-error`, reported from `componentDidCatch`, is already\n // commit-phase-safe — deferring it too is harmless and keeps one code path.\n // This is client-only (NodeRenderer's own onDiagnostic call is unchanged\n // and stays synchronous — see nodes-renderer.tsx for why SSR needs that):\n // `ServerExperienceRenderer` threads a plain synchronous array-push\n // closure instead of this setState-backed one, so SSR is unaffected.\n const onDiagnostic = useCallback((error: Error) => {\n queueMicrotask(() => {\n // Dedup by message, the guard `NodeRenderer` used to hold in a `useRef`.\n // It lives here so that module stays hook-free and therefore usable from\n // a React Server Component. Returning `prev` unchanged also skips a\n // pointless re-render, which matters more here than on the server: every\n // ancestor re-render re-reports the same diagnostic.\n setRenderDiagnostics((prev) =>\n prev.some((seen) => seen.message === error.message) ? prev : [...prev, error]\n );\n });\n }, []);\n\n // Seed from the plan so first paint matches the server renderer. Computed\n // before the `experience` guard because the hook below cannot be called\n // conditionally.\n const seedViewportId =\n initialViewportId ??\n (experience ? experience.viewports[experience.fallbackViewportIndex]?.id : undefined);\n const { activeViewportIndex } = useActiveViewport(experience?.viewports ?? [], seedViewportId);\n if (!experience) return null;\n // `??`, not `||`, so an explicit `debug={false}` overrides a debug-on plan.\n const resolvedDebug = debug ?? experience.debug;\n // Copy so the context shares no object identity with the plan arrays — see\n // the note in `server-renderer.tsx`.\n const contextViewports = experience.viewports.map((v) => ({ ...v }));\n const activeViewport = { ...(experience.viewports[activeViewportIndex] ?? FALLBACK_VIEWPORT) };\n\n const renderContext: RenderContext = {\n ...DEFAULT_CONTEXT,\n debug: resolvedDebug,\n metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...(metadata ?? {}) },\n viewports: contextViewports,\n activeViewport,\n activeViewportIndex,\n fallbackViewportIndex: experience.fallbackViewportIndex,\n };\n\n return (\n <ExperienceProvider value={renderContext}>\n {/*\n Established here, not in `ServerExperienceRenderer`: this Provider\n (and the closure it carries) never crosses a Server→Client boundary\n because everything from `ClientExperienceRenderer` down is already\n client-rendered. `ComponentErrorBoundary` reads it via `contextType`\n to report a render-time throw without receiving a callback prop —\n see component-error-boundary.tsx for why that specifically matters.\n */}\n <DiagnosticReporterContext.Provider value={onDiagnostic}>\n <NodesRenderer\n nodes={experience.nodes}\n config={config}\n viewports={experience.viewports}\n activeViewportIndex={activeViewportIndex}\n fallbackViewportIndex={experience.fallbackViewportIndex}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n </DiagnosticReporterContext.Provider>\n {resolvedDebug ? (\n <DebugExperience\n experience={experience}\n errors={[...(experience.diagnostics ?? []), ...renderDiagnostics]}\n />\n ) : null}\n </ExperienceProvider>\n );\n}\n"],"mappings":";AAkII,SAUI,KAVJ;AAzHJ,SAAS,aAAa,gBAAgC;AAQtD,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,qBAA2D;AAEpE,SAAS,yBAAyB;AAElC,MAAM,kBAAqC;AAAA,EACzC,OAAO;AAAA,EACP,UAAU,CAAC;AAAA,EACX,WAAW,CAAC;AACd;AAEA,MAAM,oBAAiC;AAAA,EACrC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,aAAa;AAAA,EACb,aAAa;AACf;AAuBO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB,cAAc;AAChB,GAA6C;AAC3C,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAkB,CAAC,CAAC;AAsBtE,QAAM,eAAe,YAAY,CAAC,UAAiB;AACjD,mBAAe,MAAM;AAMnB;AAAA,QAAqB,CAAC,SACpB,KAAK,KAAK,CAAC,SAAS,KAAK,YAAY,MAAM,OAAO,IAAI,OAAO,CAAC,GAAG,MAAM,KAAK;AAAA,MAC9E;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAKL,QAAM,iBACJ,sBACC,aAAa,WAAW,UAAU,WAAW,qBAAqB,GAAG,KAAK;AAC7E,QAAM,EAAE,oBAAoB,IAAI,kBAAkB,YAAY,aAAa,CAAC,GAAG,cAAc;AAC7F,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,gBAAgB,SAAS,WAAW;AAG1C,QAAM,mBAAmB,WAAW,UAAU,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AACnE,QAAM,iBAAiB,EAAE,GAAI,WAAW,UAAU,mBAAmB,KAAK,kBAAmB;AAE7F,QAAM,gBAA+B;AAAA,IACnC,GAAG;AAAA,IACH,OAAO;AAAA,IACP,UAAU,EAAE,GAAG,gBAAgB,UAAU,GAAG,WAAW,UAAU,GAAI,YAAY,CAAC,EAAG;AAAA,IACrF,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,uBAAuB,WAAW;AAAA,EACpC;AAEA,SACE,qBAAC,sBAAmB,OAAO,eASzB;AAAA,wBAAC,0BAA0B,UAA1B,EAAmC,OAAO,cACzC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,WAAW;AAAA,QAClB;AAAA,QACA,WAAW,WAAW;AAAA,QACtB;AAAA,QACA,uBAAuB,WAAW;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF,GACF;AAAA,IACC,gBACC;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,QAAQ,CAAC,GAAI,WAAW,eAAe,CAAC,GAAI,GAAG,iBAAiB;AAAA;AAAA,IAClE,IACE;AAAA,KACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/client-renderer.tsx"],"sourcesContent":["/*\n * Client Experience renderer. It renders the resolved plan directly, with no\n * browser-only design-property work.\n */\n\n'use client';\n\nimport { useCallback, useState, type ReactNode } from 'react';\n\nimport type { ExperienceContext, PortableRenderPlan } from '@contentful/experiences-sdk-core';\n\nimport { ComponentError } from './component-error';\nimport { DiagnosticReporterContext } from './component-error-boundary';\nimport { DebugExperience } from './debug-experience';\nimport { ExperienceProvider } from './context';\nimport { MissingComponent } from './missing-component';\nimport { NodesRenderer, type RenderError, type RenderUnknown } from './nodes-renderer';\nimport type { Config, RenderContext } from './types';\n\nconst DEFAULT_CONTEXT: ExperienceContext = {\n debug: false,\n metadata: {},\n};\n\nexport interface ClientExperienceRendererProps {\n experience: PortableRenderPlan | null | undefined;\n config: Config;\n /** Shallow-merges over the plan's `metadata`. Only needed to override it. */\n metadata?: Record<string, unknown>;\n /**\n * Observability switch. When on: renders the visible missing-component box,\n * turns the default `renderUnknown` fallback into the debug component, and\n * auto-mounts `<DebugExperience>` after the tree. Defaults to the plan's `debug`.\n */\n debug?: boolean;\n renderUnknown?: RenderUnknown;\n /** Override the fallback rendered when a registered component throws. */\n renderError?: RenderError;\n}\n\nexport function ClientExperienceRenderer({\n experience,\n config,\n metadata,\n debug,\n renderUnknown = MissingComponent,\n renderError = ComponentError,\n}: ClientExperienceRendererProps): ReactNode {\n const [renderDiagnostics, setRenderDiagnostics] = useState<Error[]>([]);\n // A `setState` updater rather than the SSR renderer's plain-array push: this\n // tree stays mounted and interactive, so a component that throws well after\n // first paint (a later re-render, an event handler) must still make\n // `<DebugExperience>` re-render with the new diagnostic — a mutated array\n // wouldn't trigger that.\n //\n // `queueMicrotask` defers the actual `setState` call: most of these\n // diagnostics (component-not-registered, malformed-slot,\n // experience-template-not-registered) are reported synchronously from\n // *inside* NodeRenderer's own render body, i.e. while React is mid-render\n // of a descendant — calling this component's setState right there is\n // exactly React's \"Cannot update a component while rendering a different\n // component\" anti-pattern. Escaping to a microtask (same \"break the\n // synchronous call chain\" rationale as core's resolveData deferral)\n // schedules the update for after the current render/commit fully unwinds.\n // `component-render-error`, reported from `componentDidCatch`, is already\n // commit-phase-safe — deferring it too is harmless and keeps one code path.\n // This is client-only (NodeRenderer's own onDiagnostic call is unchanged\n // and stays synchronous — see nodes-renderer.tsx for why SSR needs that):\n // `ServerExperienceRenderer` threads a plain synchronous array-push\n // closure instead of this setState-backed one, so SSR is unaffected.\n const onDiagnostic = useCallback((error: Error) => {\n queueMicrotask(() => {\n // Dedup by message, the guard `NodeRenderer` used to hold in a `useRef`.\n // It lives here so that module stays hook-free and therefore usable from\n // a React Server Component. Returning `prev` unchanged also skips a\n // pointless re-render, which matters more here than on the server: every\n // ancestor re-render re-reports the same diagnostic.\n setRenderDiagnostics((prev) =>\n prev.some((seen) => seen.message === error.message) ? prev : [...prev, error]\n );\n });\n }, []);\n\n if (!experience) return null;\n // `??`, not `||`, so an explicit `debug={false}` overrides a debug-on plan.\n const resolvedDebug = debug ?? experience.debug;\n const renderContext: RenderContext = {\n ...DEFAULT_CONTEXT,\n debug: resolvedDebug,\n metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...(metadata ?? {}) },\n };\n\n return (\n <ExperienceProvider value={renderContext}>\n {/*\n Established here, not in `ServerExperienceRenderer`: this Provider\n (and the closure it carries) never crosses a Server→Client boundary\n because everything from `ClientExperienceRenderer` down is already\n client-rendered. `ComponentErrorBoundary` reads it via `contextType`\n to report a render-time throw without receiving a callback prop —\n see component-error-boundary.tsx for why that specifically matters.\n */}\n <DiagnosticReporterContext.Provider value={onDiagnostic}>\n <NodesRenderer\n nodes={experience.nodes}\n config={config}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n </DiagnosticReporterContext.Provider>\n {resolvedDebug ? (\n <DebugExperience\n experience={experience}\n errors={[...(experience.diagnostics ?? []), ...renderDiagnostics]}\n />\n ) : null}\n </ExperienceProvider>\n );\n}\n"],"mappings":";AA6FI,SAUI,KAVJ;AAtFJ,SAAS,aAAa,gBAAgC;AAItD,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,qBAA2D;AAGpE,MAAM,kBAAqC;AAAA,EACzC,OAAO;AAAA,EACP,UAAU,CAAC;AACb;AAkBO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB,cAAc;AAChB,GAA6C;AAC3C,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAkB,CAAC,CAAC;AAsBtE,QAAM,eAAe,YAAY,CAAC,UAAiB;AACjD,mBAAe,MAAM;AAMnB;AAAA,QAAqB,CAAC,SACpB,KAAK,KAAK,CAAC,SAAS,KAAK,YAAY,MAAM,OAAO,IAAI,OAAO,CAAC,GAAG,MAAM,KAAK;AAAA,MAC9E;AAAA,IACF,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,gBAAgB,SAAS,WAAW;AAC1C,QAAM,gBAA+B;AAAA,IACnC,GAAG;AAAA,IACH,OAAO;AAAA,IACP,UAAU,EAAE,GAAG,gBAAgB,UAAU,GAAG,WAAW,UAAU,GAAI,YAAY,CAAC,EAAG;AAAA,EACvF;AAEA,SACE,qBAAC,sBAAmB,OAAO,eASzB;AAAA,wBAAC,0BAA0B,UAA1B,EAAmC,OAAO,cACzC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,WAAW;AAAA,QAClB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF,GACF;AAAA,IACC,gBACC;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,QAAQ,CAAC,GAAI,WAAW,eAAe,CAAC,GAAI,GAAG,iBAAiB;AAAA;AAAA,IAClE,IACE;AAAA,KACN;AAEJ;","names":[]}
package/dist/context.d.ts CHANGED
@@ -19,9 +19,8 @@ declare function ResolvedDesignProvider({ value, children, }: {
19
19
  children: ReactNode;
20
20
  }): ReactNode;
21
21
  /**
22
- * Read the current Experience runtime context — viewports, the active
23
- * viewport, the `debug` flag, and free-form metadata. Throws when called
24
- * outside any Experience renderer subtree.
22
+ * Read the current Experience runtime context — the `debug` flag and
23
+ * free-form metadata. Throws when called outside any Experience renderer subtree.
25
24
  */
26
25
  declare function useExperience(): RenderContext;
27
26
  /**
@@ -38,8 +37,8 @@ declare function useContentfulComponent(): ContentfulComponent | null;
38
37
  declare function useContentfulExperienceTemplate(): ContentfulExperienceTemplate | null;
39
38
  /**
40
39
  * Read the design values the renderer already resolved for the enclosing
41
- * node or experience template — viewport-cascaded and token-resolved, the same
42
- * bag that feeds the component's props. Returns `null` outside a rendered
40
+ * node or experience template — token-resolved, the same bag that feeds the
41
+ * component's props. Returns `null` outside a rendered
43
42
  * node / experience template, which `useDesignValues` treats as "nothing to read."
44
43
  */
45
44
  declare function useResolvedDesign(): Record<string, unknown> | null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.tsx"],"sourcesContent":["/*\n * React context providers + hooks that expose runtime context and the raw\n * Contentful payload to descendants of a rendered Experience. Customer\n * components receive only their declared props via the renderer; anything\n * that needs the Experience runtime context or the underlying payload calls\n * the hook explicitly at the top of its component body.\n */\n\n'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\n\nimport type { ContentfulComponent, ContentfulExperienceTemplate, RenderContext } from './types';\n\nconst ExperienceContext = createContext<RenderContext | null>(null);\nconst ContentfulComponentContext = createContext<ContentfulComponent | null>(null);\nconst ContentfulExperienceTemplateContext = createContext<ContentfulExperienceTemplate | null>(\n null\n);\n// Plain data only (never the Config or its functions) so it crosses RSC cleanly.\nconst ResolvedDesignContext = createContext<Record<string, unknown> | null>(null);\n\n// Wrapped as real client-component functions, not re-exported `Context.Provider`\n// objects — the server renderer imports these across the RSC boundary and\n// Next.js requires a function reference there (\"Element type is invalid\" otherwise).\n\nexport function ExperienceProvider({\n value,\n children,\n}: {\n value: RenderContext;\n children: ReactNode;\n}): ReactNode {\n return <ExperienceContext.Provider value={value}>{children}</ExperienceContext.Provider>;\n}\n\nexport function ContentfulComponentProvider({\n value,\n children,\n}: {\n value: ContentfulComponent;\n children: ReactNode;\n}): ReactNode {\n return (\n <ContentfulComponentContext.Provider value={value}>\n {children}\n </ContentfulComponentContext.Provider>\n );\n}\n\nexport function ContentfulExperienceTemplateProvider({\n value,\n children,\n}: {\n value: ContentfulExperienceTemplate;\n children: ReactNode;\n}): ReactNode {\n return (\n <ContentfulExperienceTemplateContext.Provider value={value}>\n {children}\n </ContentfulExperienceTemplateContext.Provider>\n );\n}\n\nexport function ResolvedDesignProvider({\n value,\n children,\n}: {\n value: Record<string, unknown>;\n children: ReactNode;\n}): ReactNode {\n return <ResolvedDesignContext.Provider value={value}>{children}</ResolvedDesignContext.Provider>;\n}\n\n/**\n * Read the current Experience runtime context — viewports, the active\n * viewport, the `debug` flag, and free-form metadata. Throws when called\n * outside any Experience renderer subtree.\n */\nexport function useExperience(): RenderContext {\n const ctx = useContext(ExperienceContext);\n if (ctx === null) {\n throw new Error(\n 'useExperience() must be called inside a <ServerExperienceRenderer> or <ClientExperienceRenderer> subtree.'\n );\n }\n return ctx;\n}\n\n/**\n * Read the raw Contentful payload for the nearest enclosing *component* node.\n * Returns `null` outside any rendered component node — including inside a coded\n * Experience Template, which publishes `useContentfulExperienceTemplate()`\n * instead.\n */\nexport function useContentfulComponent(): ContentfulComponent | null {\n return useContext(ContentfulComponentContext);\n}\n\n/**\n * Read the raw Contentful payload for the nearest enclosing Experience Template\n * node. Returns `null` outside an Experience Template's render tree.\n */\nexport function useContentfulExperienceTemplate(): ContentfulExperienceTemplate | null {\n return useContext(ContentfulExperienceTemplateContext);\n}\n\n/**\n * Read the design values the renderer already resolved for the enclosing\n * node or experience template — viewport-cascaded and token-resolved, the same\n * bag that feeds the component's props. Returns `null` outside a rendered\n * node / experience template, which `useDesignValues` treats as \"nothing to read.\"\n */\nexport function useResolvedDesign(): Record<string, unknown> | null {\n return useContext(ResolvedDesignContext);\n}\n"],"mappings":";AAiCS;AAvBT,SAAS,eAAe,kBAAkC;AAI1D,MAAM,oBAAoB,cAAoC,IAAI;AAClE,MAAM,6BAA6B,cAA0C,IAAI;AACjF,MAAM,sCAAsC;AAAA,EAC1C;AACF;AAEA,MAAM,wBAAwB,cAA8C,IAAI;AAMzE,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGc;AACZ,SAAO,oBAAC,kBAAkB,UAAlB,EAA2B,OAAe,UAAS;AAC7D;AAEO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AACF,GAGc;AACZ,SACE,oBAAC,2BAA2B,UAA3B,EAAoC,OAClC,UACH;AAEJ;AAEO,SAAS,qCAAqC;AAAA,EACnD;AAAA,EACA;AACF,GAGc;AACZ,SACE,oBAAC,oCAAoC,UAApC,EAA6C,OAC3C,UACH;AAEJ;AAEO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AACF,GAGc;AACZ,SAAO,oBAAC,sBAAsB,UAAtB,EAA+B,OAAe,UAAS;AACjE;AAOO,SAAS,gBAA+B;AAC7C,QAAM,MAAM,WAAW,iBAAiB;AACxC,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,yBAAqD;AACnE,SAAO,WAAW,0BAA0B;AAC9C;AAMO,SAAS,kCAAuE;AACrF,SAAO,WAAW,mCAAmC;AACvD;AAQO,SAAS,oBAAoD;AAClE,SAAO,WAAW,qBAAqB;AACzC;","names":[]}
1
+ {"version":3,"sources":["../src/context.tsx"],"sourcesContent":["/*\n * React context providers + hooks that expose runtime context and the raw\n * Contentful payload to descendants of a rendered Experience. Customer\n * components receive only their declared props via the renderer; anything\n * that needs the Experience runtime context or the underlying payload calls\n * the hook explicitly at the top of its component body.\n */\n\n'use client';\n\nimport { createContext, useContext, type ReactNode } from 'react';\n\nimport type { ContentfulComponent, ContentfulExperienceTemplate, RenderContext } from './types';\n\nconst ExperienceContext = createContext<RenderContext | null>(null);\nconst ContentfulComponentContext = createContext<ContentfulComponent | null>(null);\nconst ContentfulExperienceTemplateContext = createContext<ContentfulExperienceTemplate | null>(\n null\n);\n// Plain data only (never the Config or its functions) so it crosses RSC cleanly.\nconst ResolvedDesignContext = createContext<Record<string, unknown> | null>(null);\n\n// Wrapped as real client-component functions, not re-exported `Context.Provider`\n// objects — the server renderer imports these across the RSC boundary and\n// Next.js requires a function reference there (\"Element type is invalid\" otherwise).\n\nexport function ExperienceProvider({\n value,\n children,\n}: {\n value: RenderContext;\n children: ReactNode;\n}): ReactNode {\n return <ExperienceContext.Provider value={value}>{children}</ExperienceContext.Provider>;\n}\n\nexport function ContentfulComponentProvider({\n value,\n children,\n}: {\n value: ContentfulComponent;\n children: ReactNode;\n}): ReactNode {\n return (\n <ContentfulComponentContext.Provider value={value}>\n {children}\n </ContentfulComponentContext.Provider>\n );\n}\n\nexport function ContentfulExperienceTemplateProvider({\n value,\n children,\n}: {\n value: ContentfulExperienceTemplate;\n children: ReactNode;\n}): ReactNode {\n return (\n <ContentfulExperienceTemplateContext.Provider value={value}>\n {children}\n </ContentfulExperienceTemplateContext.Provider>\n );\n}\n\nexport function ResolvedDesignProvider({\n value,\n children,\n}: {\n value: Record<string, unknown>;\n children: ReactNode;\n}): ReactNode {\n return <ResolvedDesignContext.Provider value={value}>{children}</ResolvedDesignContext.Provider>;\n}\n\n/**\n * Read the current Experience runtime context — the `debug` flag and\n * free-form metadata. Throws when called outside any Experience renderer subtree.\n */\nexport function useExperience(): RenderContext {\n const ctx = useContext(ExperienceContext);\n if (ctx === null) {\n throw new Error(\n 'useExperience() must be called inside a <ServerExperienceRenderer> or <ClientExperienceRenderer> subtree.'\n );\n }\n return ctx;\n}\n\n/**\n * Read the raw Contentful payload for the nearest enclosing *component* node.\n * Returns `null` outside any rendered component node — including inside a coded\n * Experience Template, which publishes `useContentfulExperienceTemplate()`\n * instead.\n */\nexport function useContentfulComponent(): ContentfulComponent | null {\n return useContext(ContentfulComponentContext);\n}\n\n/**\n * Read the raw Contentful payload for the nearest enclosing Experience Template\n * node. Returns `null` outside an Experience Template's render tree.\n */\nexport function useContentfulExperienceTemplate(): ContentfulExperienceTemplate | null {\n return useContext(ContentfulExperienceTemplateContext);\n}\n\n/**\n * Read the design values the renderer already resolved for the enclosing\n * node or experience template — token-resolved, the same bag that feeds the\n * component's props. Returns `null` outside a rendered\n * node / experience template, which `useDesignValues` treats as \"nothing to read.\"\n */\nexport function useResolvedDesign(): Record<string, unknown> | null {\n return useContext(ResolvedDesignContext);\n}\n"],"mappings":";AAiCS;AAvBT,SAAS,eAAe,kBAAkC;AAI1D,MAAM,oBAAoB,cAAoC,IAAI;AAClE,MAAM,6BAA6B,cAA0C,IAAI;AACjF,MAAM,sCAAsC;AAAA,EAC1C;AACF;AAEA,MAAM,wBAAwB,cAA8C,IAAI;AAMzE,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AACF,GAGc;AACZ,SAAO,oBAAC,kBAAkB,UAAlB,EAA2B,OAAe,UAAS;AAC7D;AAEO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AACF,GAGc;AACZ,SACE,oBAAC,2BAA2B,UAA3B,EAAoC,OAClC,UACH;AAEJ;AAEO,SAAS,qCAAqC;AAAA,EACnD;AAAA,EACA;AACF,GAGc;AACZ,SACE,oBAAC,oCAAoC,UAApC,EAA6C,OAC3C,UACH;AAEJ;AAEO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AACF,GAGc;AACZ,SAAO,oBAAC,sBAAsB,UAAtB,EAA+B,OAAe,UAAS;AACjE;AAMO,SAAS,gBAA+B;AAC7C,QAAM,MAAM,WAAW,iBAAiB;AACxC,MAAI,QAAQ,MAAM;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAQO,SAAS,yBAAqD;AACnE,SAAO,WAAW,0BAA0B;AAC9C;AAMO,SAAS,kCAAuE;AACrF,SAAO,WAAW,mCAAmC;AACvD;AAQO,SAAS,oBAAoD;AAClE,SAAO,WAAW,qBAAqB;AACzC;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/debug-experience.tsx"],"sourcesContent":["/*\n * First-party debug panel. Renders the resolved Experience plan as pretty\n * JSON so a customer can see exactly what the SDK interpreted from the payload\n * — node tree, registrations, resolved props, viewports.\n *\n * Two ways to use it:\n * - Auto-mounted by the renderers when `debug` is on (above the tree).\n * - Mounted manually anywhere: `<DebugExperience experience={plan} />`.\n *\n * v1 is just the JSON dump wrapped in a native <details> so it collapses\n * without any client JS (safe in server components). Room to grow into a\n * node-tree explorer, viewport indicator, and resolveData timing panel — kept\n * deliberately small for now.\n *\n * No React hooks here on purpose: it must render in both server and client\n * renderer subtrees.\n */\n\nimport type { ReactNode } from 'react';\n\nimport type { PortableRenderPlan } from '@contentful/experiences-sdk-core';\n\nexport interface DebugExperienceProps {\n /** The resolved plan to inspect (what a renderer receives as `experience`). */\n experience: PortableRenderPlan;\n /** Start expanded. Defaults to collapsed to stay out of the way. */\n defaultOpen?: boolean;\n /**\n * Resolve-time + render-time diagnostics for this render, merged by the\n * caller (`ServerExperienceRenderer` / `ClientExperienceRenderer`). Plain\n * `Error`s — the two cases that wrap a real caught exception\n * (`component-render-error`, `resolve-data-failed`) set `.cause` to the\n * original error, inspectable via `console.error` or `error.cause.stack`.\n * Defaults to `[]` for a manually-mounted `<DebugExperience>`.\n */\n errors?: Error[];\n}\n\nexport function DebugExperience({\n experience,\n defaultOpen = false,\n errors = [],\n}: DebugExperienceProps): ReactNode {\n const nodeCount = experience.nodes.length;\n // Coded Experience Templates are ordinary top-level nodes, so name them from\n // the node list — a composite Experience simply has none to report.\n const templateIds = experience.nodes\n .filter((node) => node.registration.kind === 'experienceTemplate')\n .map((node) => node.registration.id);\n const summary = `Experience debug — ${nodeCount} top-level node${nodeCount === 1 ? '' : 's'}${\n templateIds.length\n ? `, experience template${templateIds.length === 1 ? '' : 's'}: ${templateIds.join(', ')}`\n : ''\n }`;\n\n // Auto-expand whenever there's something to see, even if the caller didn't\n // explicitly ask — a beta customer shouldn't have to know to click into a\n // collapsed panel to discover that something went wrong.\n const open = defaultOpen || errors.length > 0;\n\n return (\n <details\n open={open}\n data-experiences-debug\n style={{\n margin: '1rem 0',\n border: '1px solid #6b7280',\n borderRadius: '6px',\n background: '#0b1021',\n color: '#e2e8f0',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: '0.75rem',\n overflow: 'hidden',\n }}\n >\n <summary\n style={{\n cursor: 'pointer',\n padding: '0.5rem 0.75rem',\n background: '#111827',\n userSelect: 'none',\n }}\n >\n {summary}\n </summary>\n {errors.length > 0 ? <DiagnosticList errors={errors} /> : null}\n <pre\n style={{\n margin: 0,\n padding: '0.75rem',\n overflow: 'auto',\n maxHeight: '32rem',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n }}\n >\n {safeStringify(experience)}\n </pre>\n </details>\n );\n}\n\n// Deliberately unstyled for now — this just needs to make the data visible,\n// not console-only. Visual treatment (grouping, color, counts) can grow later.\nfunction DiagnosticList({ errors }: { errors: Error[] }): ReactNode {\n return (\n <ul data-experiences-debug-errors>\n {errors.map((error, index) => (\n <li key={index}>{error.message}</li>\n ))}\n </ul>\n );\n}\n\n/**\n * JSON.stringify with a circular-reference guard. A resolved plan is plain\n * data, but a customer's `resolveData` could stash a non-serializable value on\n * `props.resolved` — degrade to a placeholder rather than throwing inside a\n * debug panel.\n */\nfunction safeStringify(value: unknown): string {\n const seen = new WeakSet<object>();\n try {\n return JSON.stringify(\n value,\n (_key, val) => {\n if (typeof val === 'object' && val !== null) {\n if (seen.has(val)) return '[Circular]';\n seen.add(val);\n }\n if (typeof val === 'function') return `[Function ${val.name || 'anonymous'}]`;\n if (typeof val === 'undefined') return '[undefined]';\n return val;\n },\n 2\n );\n } catch (err) {\n // `err` isn't guaranteed to be an Error — a getter deep in customer data\n // (a resolveData result, resolved design values) can `throw null` or\n // `throw 'reason'` during JSON.stringify's replacer walk, and this\n // fallback's whole job is to never throw itself.\n const reason = err instanceof Error ? err.message : String(err);\n return `[DebugExperience: could not serialize plan — ${reason}]`;\n }\n}\n"],"mappings":"AA6DI,SAcE,KAdF;AAvBG,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,cAAc;AAAA,EACd,SAAS,CAAC;AACZ,GAAoC;AAClC,QAAM,YAAY,WAAW,MAAM;AAGnC,QAAM,cAAc,WAAW,MAC5B,OAAO,CAAC,SAAS,KAAK,aAAa,SAAS,oBAAoB,EAChE,IAAI,CAAC,SAAS,KAAK,aAAa,EAAE;AACrC,QAAM,UAAU,2BAAsB,SAAS,kBAAkB,cAAc,IAAI,KAAK,GAAG,GACzF,YAAY,SACR,wBAAwB,YAAY,WAAW,IAAI,KAAK,GAAG,KAAK,YAAY,KAAK,IAAI,CAAC,KACtF,EACN;AAKA,QAAM,OAAO,eAAe,OAAO,SAAS;AAE5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,0BAAsB;AAAA,MACtB,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,SAAS;AAAA,cACT,YAAY;AAAA,cACZ,YAAY;AAAA,YACd;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACC,OAAO,SAAS,IAAI,oBAAC,kBAAe,QAAgB,IAAK;AAAA,QAC1D;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,SAAS;AAAA,cACT,UAAU;AAAA,cACV,WAAW;AAAA,cACX,YAAY;AAAA,cACZ,WAAW;AAAA,YACb;AAAA,YAEC,wBAAc,UAAU;AAAA;AAAA,QAC3B;AAAA;AAAA;AAAA,EACF;AAEJ;AAIA,SAAS,eAAe,EAAE,OAAO,GAAmC;AAClE,SACE,oBAAC,QAAG,iCAA6B,MAC9B,iBAAO,IAAI,CAAC,OAAO,UAClB,oBAAC,QAAgB,gBAAM,WAAd,KAAsB,CAChC,GACH;AAEJ;AAQA,SAAS,cAAc,OAAwB;AAC7C,QAAM,OAAO,oBAAI,QAAgB;AACjC,MAAI;AACF,WAAO,KAAK;AAAA,MACV;AAAA,MACA,CAAC,MAAM,QAAQ;AACb,YAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,cAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,eAAK,IAAI,GAAG;AAAA,QACd;AACA,YAAI,OAAO,QAAQ,WAAY,QAAO,aAAa,IAAI,QAAQ,WAAW;AAC1E,YAAI,OAAO,QAAQ,YAAa,QAAO;AACvC,eAAO;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AAKZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,WAAO,qDAAgD,MAAM;AAAA,EAC/D;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/debug-experience.tsx"],"sourcesContent":["/*\n * First-party debug panel. Renders the resolved Experience plan as pretty\n * JSON so a customer can see exactly what the SDK interpreted from the payload\n * — node tree, registrations, and resolved props.\n *\n * Two ways to use it:\n * - Auto-mounted by the renderers when `debug` is on (above the tree).\n * - Mounted manually anywhere: `<DebugExperience experience={plan} />`.\n *\n * v1 is just the JSON dump wrapped in a native <details> so it collapses\n * without any client JS (safe in server components). Room to grow into a\n * node-tree explorer and resolveData timing panel — kept\n * deliberately small for now.\n *\n * No React hooks here on purpose: it must render in both server and client\n * renderer subtrees.\n */\n\nimport type { ReactNode } from 'react';\n\nimport type { PortableRenderPlan } from '@contentful/experiences-sdk-core';\n\nexport interface DebugExperienceProps {\n /** The resolved plan to inspect (what a renderer receives as `experience`). */\n experience: PortableRenderPlan;\n /** Start expanded. Defaults to collapsed to stay out of the way. */\n defaultOpen?: boolean;\n /**\n * Resolve-time + render-time diagnostics for this render, merged by the\n * caller (`ServerExperienceRenderer` / `ClientExperienceRenderer`). Plain\n * `Error`s — the two cases that wrap a real caught exception\n * (`component-render-error`, `resolve-data-failed`) set `.cause` to the\n * original error, inspectable via `console.error` or `error.cause.stack`.\n * Defaults to `[]` for a manually-mounted `<DebugExperience>`.\n */\n errors?: Error[];\n}\n\nexport function DebugExperience({\n experience,\n defaultOpen = false,\n errors = [],\n}: DebugExperienceProps): ReactNode {\n const nodeCount = experience.nodes.length;\n // Coded Experience Templates are ordinary top-level nodes, so name them from\n // the node list — a composite Experience simply has none to report.\n const templateIds = experience.nodes\n .filter((node) => node.registration.kind === 'experienceTemplate')\n .map((node) => node.registration.id);\n const summary = `Experience debug — ${nodeCount} top-level node${nodeCount === 1 ? '' : 's'}${\n templateIds.length\n ? `, experience template${templateIds.length === 1 ? '' : 's'}: ${templateIds.join(', ')}`\n : ''\n }`;\n\n // Auto-expand whenever there's something to see, even if the caller didn't\n // explicitly ask — a beta customer shouldn't have to know to click into a\n // collapsed panel to discover that something went wrong.\n const open = defaultOpen || errors.length > 0;\n\n return (\n <details\n open={open}\n data-experiences-debug\n style={{\n margin: '1rem 0',\n border: '1px solid #6b7280',\n borderRadius: '6px',\n background: '#0b1021',\n color: '#e2e8f0',\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: '0.75rem',\n overflow: 'hidden',\n }}\n >\n <summary\n style={{\n cursor: 'pointer',\n padding: '0.5rem 0.75rem',\n background: '#111827',\n userSelect: 'none',\n }}\n >\n {summary}\n </summary>\n {errors.length > 0 ? <DiagnosticList errors={errors} /> : null}\n <pre\n style={{\n margin: 0,\n padding: '0.75rem',\n overflow: 'auto',\n maxHeight: '32rem',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n }}\n >\n {safeStringify(experience)}\n </pre>\n </details>\n );\n}\n\n// Deliberately unstyled for now — this just needs to make the data visible,\n// not console-only. Visual treatment (grouping, color, counts) can grow later.\nfunction DiagnosticList({ errors }: { errors: Error[] }): ReactNode {\n return (\n <ul data-experiences-debug-errors>\n {errors.map((error, index) => (\n <li key={index}>{error.message}</li>\n ))}\n </ul>\n );\n}\n\n/**\n * JSON.stringify with a circular-reference guard. A resolved plan is plain\n * data, but a customer's `resolveData` could stash a non-serializable value on\n * `props.resolved` — degrade to a placeholder rather than throwing inside a\n * debug panel.\n */\nfunction safeStringify(value: unknown): string {\n const seen = new WeakSet<object>();\n try {\n return JSON.stringify(\n value,\n (_key, val) => {\n if (typeof val === 'object' && val !== null) {\n if (seen.has(val)) return '[Circular]';\n seen.add(val);\n }\n if (typeof val === 'function') return `[Function ${val.name || 'anonymous'}]`;\n if (typeof val === 'undefined') return '[undefined]';\n return val;\n },\n 2\n );\n } catch (err) {\n // `err` isn't guaranteed to be an Error — a getter deep in customer data\n // (a resolveData result, resolved design values) can `throw null` or\n // `throw 'reason'` during JSON.stringify's replacer walk, and this\n // fallback's whole job is to never throw itself.\n const reason = err instanceof Error ? err.message : String(err);\n return `[DebugExperience: could not serialize plan — ${reason}]`;\n }\n}\n"],"mappings":"AA6DI,SAcE,KAdF;AAvBG,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,cAAc;AAAA,EACd,SAAS,CAAC;AACZ,GAAoC;AAClC,QAAM,YAAY,WAAW,MAAM;AAGnC,QAAM,cAAc,WAAW,MAC5B,OAAO,CAAC,SAAS,KAAK,aAAa,SAAS,oBAAoB,EAChE,IAAI,CAAC,SAAS,KAAK,aAAa,EAAE;AACrC,QAAM,UAAU,2BAAsB,SAAS,kBAAkB,cAAc,IAAI,KAAK,GAAG,GACzF,YAAY,SACR,wBAAwB,YAAY,WAAW,IAAI,KAAK,GAAG,KAAK,YAAY,KAAK,IAAI,CAAC,KACtF,EACN;AAKA,QAAM,OAAO,eAAe,OAAO,SAAS;AAE5C,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,0BAAsB;AAAA,MACtB,OAAO;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,OAAO;AAAA,QACP,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,UAAU;AAAA,MACZ;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,SAAS;AAAA,cACT,YAAY;AAAA,cACZ,YAAY;AAAA,YACd;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QACC,OAAO,SAAS,IAAI,oBAAC,kBAAe,QAAgB,IAAK;AAAA,QAC1D;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,cACL,QAAQ;AAAA,cACR,SAAS;AAAA,cACT,UAAU;AAAA,cACV,WAAW;AAAA,cACX,YAAY;AAAA,cACZ,WAAW;AAAA,YACb;AAAA,YAEC,wBAAc,UAAU;AAAA;AAAA,QAC3B;AAAA;AAAA;AAAA,EACF;AAEJ;AAIA,SAAS,eAAe,EAAE,OAAO,GAAmC;AAClE,SACE,oBAAC,QAAG,iCAA6B,MAC9B,iBAAO,IAAI,CAAC,OAAO,UAClB,oBAAC,QAAgB,gBAAM,WAAd,KAAsB,CAChC,GACH;AAEJ;AAQA,SAAS,cAAc,OAAwB;AAC7C,QAAM,OAAO,oBAAI,QAAgB;AACjC,MAAI;AACF,WAAO,KAAK;AAAA,MACV;AAAA,MACA,CAAC,MAAM,QAAQ;AACb,YAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AAC3C,cAAI,KAAK,IAAI,GAAG,EAAG,QAAO;AAC1B,eAAK,IAAI,GAAG;AAAA,QACd;AACA,YAAI,OAAO,QAAQ,WAAY,QAAO,aAAa,IAAI,QAAQ,WAAW;AAC1E,YAAI,OAAO,QAAQ,YAAa,QAAO;AACvC,eAAO;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AAKZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,WAAO,qDAAgD,MAAM;AAAA,EAC/D;AACF;","names":[]}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,6 @@ export { ServerExperienceRenderer, ServerExperienceRendererProps } from './serve
3
3
  export { MissingComponent, MissingComponentProps } from './missing-component.js';
4
4
  export { ComponentError, ComponentErrorProps } from './component-error.js';
5
5
  export { DebugExperience, DebugExperienceProps } from './debug-experience.js';
6
- export { UseActiveViewportResult, useActiveViewport } from './use-active-viewport.js';
7
6
  export { DiagnosticReporter, RenderError, RenderUnknown } from './nodes-renderer.js';
8
7
  export { UseLivePreviewExperienceOptions, UseLivePreviewExperienceResult, useLivePreviewExperience } from './use-live-preview-experience.js';
9
8
  export { UseLivePreviewOptions, UseLivePreviewResult, useLivePreview } from './use-live-preview.js';
@@ -13,7 +12,7 @@ export { useContentfulComponent, useContentfulExperienceTemplate, useExperience
13
12
  export { useDesignValues } from './use-design-values.js';
14
13
  export { ToCssOptions, toCss } from './design-utils.js';
15
14
  export { ComponentConfig, Components, Config, ContentfulComponent, ContentfulExperienceTemplate, ExperienceTemplateConfig, ExperienceTemplateRegistration, ExperienceTemplates, Registration, defineComponent, defineExperienceTemplate, normalizeComponentRegistration, normalizeExperienceTemplateRegistration } from './types.js';
16
- export { ComponentNode, ComponentRef, DesignPropValue, DesignToken, ExperienceContext, ExperienceNode, ExperiencePayload, ExperienceSourceMap, ExperienceSys, ExperienceTemplateNode, ExperienceTemplateRef, ManualDesignValue, PortableRegistration, PortableRenderNode, PortableRenderPlan, RenderContext, ResolveContext, ResolveExperienceOptions, ResolveToken, ResolverConfig, ValuesByViewport, ViewportDef, resolveExperience } from '@contentful/experiences-sdk-core';
15
+ export { ComponentNode, ComponentRef, DesignPropValue, DesignToken, ExperienceContext, ExperienceNode, ExperiencePayload, ExperienceSourceMap, ExperienceSys, ExperienceTemplateNode, ExperienceTemplateRef, ManualDesignValue, PortableRegistration, PortableRenderNode, PortableRenderPlan, RenderContext, ResolveContext, ResolveExperienceOptions, ResolveToken, ResolverConfig, resolveExperience } from '@contentful/experiences-sdk-core';
17
16
  export { ANONYMOUS_ID_COOKIE, ByDestinationNodeIdExperienceOptions, ByDestinationPathExperienceOptions, ByIdExperienceOptions, ClickBuilderArgs, ClientOptions, ContentfulViewDelivery, ContentfulViewDeliveryClient, CreateClientOptions, DEFAULT_PAGE_PROPERTIES, DELIVERY_HOST, DestinationRedirectResult, EventBuilder, EventBuilderConfig, ExperienceFetchError, ExperienceOptions, ExperienceResponse, FlagViewBuilderArgs, HoverBuilderArgs, IdentifyBuilderArgs, InteractionBuilderArgsBase, NotFoundError, PREVIEW_HOST, PageViewBuilderArgs, ResolveOptions, TrackBuilderArgs, UniversalEventBuilderArgs, ViewBuilderArgs, createClient, fetchExperience, readSourceMap, toExperiencePayload, toExperiencePayloadFromDestination } from '@contentful/experiences-client';
18
- export { CSS_PROPERTIES, getValueForViewport, getViewportIndex, isCssProperty, resolveDesignProperties, toCssKey, toCssMediaQuery } from '@contentful/experiences-design';
17
+ export { CSS_PROPERTIES, isCssProperty, toCssKey } from '@contentful/experiences-design';
19
18
  import 'react';
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ import { ServerExperienceRenderer } from "./server-renderer";
6
6
  import { MissingComponent } from "./missing-component";
7
7
  import { ComponentError } from "./component-error";
8
8
  import { DebugExperience } from "./debug-experience";
9
- import { useActiveViewport } from "./use-active-viewport";
10
9
  import { useLivePreviewExperience } from "./use-live-preview-experience";
11
10
  import { useLivePreview } from "./use-live-preview";
12
11
  import { useExperiencePlan } from "./use-experience-plan";
@@ -27,15 +26,7 @@ import {
27
26
  } from "./types";
28
27
  import { resolveExperience } from "@contentful/experiences-sdk-core";
29
28
  import { DEFAULT_PAGE_PROPERTIES, EventBuilder } from "@contentful/experiences-client";
30
- import {
31
- CSS_PROPERTIES,
32
- getValueForViewport,
33
- getViewportIndex,
34
- isCssProperty,
35
- resolveDesignProperties,
36
- toCssKey,
37
- toCssMediaQuery
38
- } from "@contentful/experiences-design";
29
+ import { CSS_PROPERTIES, isCssProperty, toCssKey } from "@contentful/experiences-design";
39
30
  import {
40
31
  ANONYMOUS_ID_COOKIE,
41
32
  ContentfulViewDelivery,
@@ -75,20 +66,15 @@ export {
75
66
  defineExperienceTemplate,
76
67
  fetchExperience,
77
68
  fetchPreviewSession,
78
- getValueForViewport,
79
- getViewportIndex,
80
69
  isCssProperty,
81
70
  normalizeComponentRegistration,
82
71
  normalizeExperienceTemplateRegistration,
83
72
  readSourceMap,
84
- resolveDesignProperties,
85
73
  resolveExperience,
86
74
  toCss,
87
75
  toCssKey,
88
- toCssMediaQuery,
89
76
  toExperiencePayload,
90
77
  toExperiencePayloadFromDestination,
91
- useActiveViewport,
92
78
  useContentfulComponent,
93
79
  useContentfulExperienceTemplate,
94
80
  useDesignValues,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/*\n * Public API surface for `@contentful/experiences-react`.\n *\n * Customers add ONLY this package to their app's dependencies. Everything\n * needed to render an Experience — types, the resolver, viewport utilities,\n * the renderer components, the authoring helpers — is re-exported from here.\n * The internal `@contentful/experiences-sdk-core` and\n * `@contentful/experiences-design` packages are workspace-only implementation\n * details; they are not part of the public API.\n *\n * `ExperienceRenderer` is an alias for `ClientExperienceRenderer`; SSR\n * consumers explicitly import `ServerExperienceRenderer`.\n */\n\n// ─── Renderers ─────────────────────────────────────────────────────────────\nexport {\n ClientExperienceRenderer as ExperienceRenderer,\n ClientExperienceRenderer,\n} from './client-renderer';\nexport type { ClientExperienceRendererProps as ExperienceRendererProps } from './client-renderer';\nexport type { ClientExperienceRendererProps } from './client-renderer';\n\nexport { ServerExperienceRenderer } from './server-renderer';\nexport type { ServerExperienceRendererProps } from './server-renderer';\n\nexport { MissingComponent } from './missing-component';\nexport type { MissingComponentProps } from './missing-component';\n\nexport { ComponentError } from './component-error';\nexport type { ComponentErrorProps } from './component-error';\n\nexport { DebugExperience } from './debug-experience';\nexport type { DebugExperienceProps } from './debug-experience';\n\nexport { useActiveViewport } from './use-active-viewport';\nexport type { UseActiveViewportResult } from './use-active-viewport';\n\nexport type { DiagnosticReporter, RenderError, RenderUnknown } from './nodes-renderer';\n\nexport { useLivePreviewExperience } from './use-live-preview-experience';\nexport type {\n UseLivePreviewExperienceOptions,\n UseLivePreviewExperienceResult,\n} from './use-live-preview-experience';\nexport { useLivePreview } from './use-live-preview';\nexport type { UseLivePreviewOptions, UseLivePreviewResult } from './use-live-preview';\n\nexport { useExperiencePlan } from './use-experience-plan';\nexport type {\n ExperiencePlanResolveOptions,\n UseExperiencePlanOptions,\n UseExperiencePlanResult,\n} from './use-experience-plan';\nexport type {\n LivePreviewClient,\n LivePreviewResult,\n PreviewSessionClientOptions,\n PreviewSessionExperienceOptions,\n PreviewSessionOptions,\n PreviewSessionResolveOptions,\n} from '@contentful/experiences-live-preview';\nexport {\n fetchPreviewSession,\n LivePreviewConnectionError,\n PREVIEW_WEBSOCKET_HOST,\n PreviewSessionFetchError,\n} from '@contentful/experiences-live-preview';\n\n// ─── Runtime context hooks ───────────────────────────────────────────────\nexport { useExperience, useContentfulComponent, useContentfulExperienceTemplate } from './context';\nexport { useDesignValues } from './use-design-values';\nexport { toCss } from './design-utils';\nexport type { ToCssOptions } from './design-utils';\n\n// ─── Authoring helpers + Config types ─────────────────────────────────────\nexport {\n defineComponent,\n defineExperienceTemplate,\n normalizeComponentRegistration,\n normalizeExperienceTemplateRegistration,\n} from './types';\nexport type {\n ComponentConfig,\n Components,\n Config,\n ContentfulComponent,\n ContentfulExperienceTemplate,\n ExperienceTemplateConfig,\n ExperienceTemplateRegistration,\n ExperienceTemplates,\n Registration,\n RenderContext,\n ResolveToken,\n} from './types';\n\n// ─── Resolver (re-exported from render-core) ──────────────────────────────\nexport { resolveExperience } from '@contentful/experiences-sdk-core';\nexport type { ResolverConfig, ResolveExperienceOptions } from '@contentful/experiences-sdk-core';\n\n// ─── Event builder (re-exported from client) ──────────────────────────────\nexport { DEFAULT_PAGE_PROPERTIES, EventBuilder } from '@contentful/experiences-client';\nexport type {\n ClickBuilderArgs,\n EventBuilderConfig,\n FlagViewBuilderArgs,\n HoverBuilderArgs,\n IdentifyBuilderArgs,\n InteractionBuilderArgsBase,\n PageViewBuilderArgs,\n TrackBuilderArgs,\n UniversalEventBuilderArgs,\n ViewBuilderArgs,\n} from '@contentful/experiences-client';\n\n// ─── Core IR + payload types (re-exported from render-core) ───────────────\nexport type {\n ComponentNode,\n ComponentRef,\n DesignPropValue,\n DesignToken,\n ExperienceContext,\n ExperienceNode,\n ExperiencePayload,\n ExperienceSourceMap,\n ExperienceSys,\n ExperienceTemplateNode,\n ExperienceTemplateRef,\n ManualDesignValue,\n PortableRegistration,\n PortableRenderNode,\n PortableRenderPlan,\n ResolveContext,\n ValuesByViewport,\n ViewportDef,\n} from '@contentful/experiences-sdk-core';\n\n// ─── Design utilities (re-exported from design) ───────────────────────────\nexport {\n CSS_PROPERTIES,\n getValueForViewport,\n getViewportIndex,\n isCssProperty,\n resolveDesignProperties,\n toCssKey,\n toCssMediaQuery,\n} from '@contentful/experiences-design';\n\n// ─── Delivery client + fetchExperience ────────────────────────────────────\nexport {\n ANONYMOUS_ID_COOKIE,\n ContentfulViewDelivery,\n ContentfulViewDeliveryClient,\n DELIVERY_HOST,\n ExperienceFetchError,\n NotFoundError,\n PREVIEW_HOST,\n createClient,\n fetchExperience,\n readSourceMap,\n toExperiencePayload,\n toExperiencePayloadFromDestination,\n} from '@contentful/experiences-client';\nexport type {\n ByDestinationNodeIdExperienceOptions,\n ByDestinationPathExperienceOptions,\n ByIdExperienceOptions,\n ClientOptions,\n CreateClientOptions,\n DestinationRedirectResult,\n ExperienceOptions,\n ExperienceResponse,\n ResolveOptions,\n} from '@contentful/experiences-client';\n"],"mappings":"AAeA;AAAA,EAC8B;AAAA,EAC5B,4BAAAA;AAAA,OACK;AAIP,SAAS,gCAAgC;AAGzC,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAG/B,SAAS,uBAAuB;AAGhC,SAAS,yBAAyB;AAKlC,SAAS,gCAAgC;AAKzC,SAAS,sBAAsB;AAG/B,SAAS,yBAAyB;AAclC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,eAAe,wBAAwB,uCAAuC;AACvF,SAAS,uBAAuB;AAChC,SAAS,aAAa;AAItB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAgBP,SAAS,yBAAyB;AAIlC,SAAS,yBAAyB,oBAAoB;AAqCtD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":["ClientExperienceRenderer"]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/*\n * Public API surface for `@contentful/experiences-react`.\n *\n * Customers add ONLY this package to their app's dependencies. Everything\n * needed to render an Experience — types, the resolver, design utilities,\n * the renderer components, the authoring helpers — is re-exported from here.\n * The internal `@contentful/experiences-sdk-core` and\n * `@contentful/experiences-design` packages are workspace-only implementation\n * details; they are not part of the public API.\n *\n * `ExperienceRenderer` is an alias for `ClientExperienceRenderer`; SSR\n * consumers explicitly import `ServerExperienceRenderer`.\n */\n\n// ─── Renderers ─────────────────────────────────────────────────────────────\nexport {\n ClientExperienceRenderer as ExperienceRenderer,\n ClientExperienceRenderer,\n} from './client-renderer';\nexport type { ClientExperienceRendererProps as ExperienceRendererProps } from './client-renderer';\nexport type { ClientExperienceRendererProps } from './client-renderer';\n\nexport { ServerExperienceRenderer } from './server-renderer';\nexport type { ServerExperienceRendererProps } from './server-renderer';\n\nexport { MissingComponent } from './missing-component';\nexport type { MissingComponentProps } from './missing-component';\n\nexport { ComponentError } from './component-error';\nexport type { ComponentErrorProps } from './component-error';\n\nexport { DebugExperience } from './debug-experience';\nexport type { DebugExperienceProps } from './debug-experience';\n\nexport type { DiagnosticReporter, RenderError, RenderUnknown } from './nodes-renderer';\n\nexport { useLivePreviewExperience } from './use-live-preview-experience';\nexport type {\n UseLivePreviewExperienceOptions,\n UseLivePreviewExperienceResult,\n} from './use-live-preview-experience';\nexport { useLivePreview } from './use-live-preview';\nexport type { UseLivePreviewOptions, UseLivePreviewResult } from './use-live-preview';\n\nexport { useExperiencePlan } from './use-experience-plan';\nexport type {\n ExperiencePlanResolveOptions,\n UseExperiencePlanOptions,\n UseExperiencePlanResult,\n} from './use-experience-plan';\nexport type {\n LivePreviewClient,\n LivePreviewResult,\n PreviewSessionClientOptions,\n PreviewSessionExperienceOptions,\n PreviewSessionOptions,\n PreviewSessionResolveOptions,\n} from '@contentful/experiences-live-preview';\nexport {\n fetchPreviewSession,\n LivePreviewConnectionError,\n PREVIEW_WEBSOCKET_HOST,\n PreviewSessionFetchError,\n} from '@contentful/experiences-live-preview';\n\n// ─── Runtime context hooks ───────────────────────────────────────────────\nexport { useExperience, useContentfulComponent, useContentfulExperienceTemplate } from './context';\nexport { useDesignValues } from './use-design-values';\nexport { toCss } from './design-utils';\nexport type { ToCssOptions } from './design-utils';\n\n// ─── Authoring helpers + Config types ─────────────────────────────────────\nexport {\n defineComponent,\n defineExperienceTemplate,\n normalizeComponentRegistration,\n normalizeExperienceTemplateRegistration,\n} from './types';\nexport type {\n ComponentConfig,\n Components,\n Config,\n ContentfulComponent,\n ContentfulExperienceTemplate,\n ExperienceTemplateConfig,\n ExperienceTemplateRegistration,\n ExperienceTemplates,\n Registration,\n RenderContext,\n ResolveToken,\n} from './types';\n\n// ─── Resolver (re-exported from render-core) ──────────────────────────────\nexport { resolveExperience } from '@contentful/experiences-sdk-core';\nexport type { ResolverConfig, ResolveExperienceOptions } from '@contentful/experiences-sdk-core';\n\n// ─── Event builder (re-exported from client) ──────────────────────────────\nexport { DEFAULT_PAGE_PROPERTIES, EventBuilder } from '@contentful/experiences-client';\nexport type {\n ClickBuilderArgs,\n EventBuilderConfig,\n FlagViewBuilderArgs,\n HoverBuilderArgs,\n IdentifyBuilderArgs,\n InteractionBuilderArgsBase,\n PageViewBuilderArgs,\n TrackBuilderArgs,\n UniversalEventBuilderArgs,\n ViewBuilderArgs,\n} from '@contentful/experiences-client';\n\n// ─── Core IR + payload types (re-exported from render-core) ───────────────\nexport type {\n ComponentNode,\n ComponentRef,\n DesignPropValue,\n DesignToken,\n ExperienceContext,\n ExperienceNode,\n ExperiencePayload,\n ExperienceSourceMap,\n ExperienceSys,\n ExperienceTemplateNode,\n ExperienceTemplateRef,\n ManualDesignValue,\n PortableRegistration,\n PortableRenderNode,\n PortableRenderPlan,\n ResolveContext,\n} from '@contentful/experiences-sdk-core';\n\n// ─── Design utilities (re-exported from design) ───────────────────────────\nexport { CSS_PROPERTIES, isCssProperty, toCssKey } from '@contentful/experiences-design';\n\n// ─── Delivery client + fetchExperience ────────────────────────────────────\nexport {\n ANONYMOUS_ID_COOKIE,\n ContentfulViewDelivery,\n ContentfulViewDeliveryClient,\n DELIVERY_HOST,\n ExperienceFetchError,\n NotFoundError,\n PREVIEW_HOST,\n createClient,\n fetchExperience,\n readSourceMap,\n toExperiencePayload,\n toExperiencePayloadFromDestination,\n} from '@contentful/experiences-client';\nexport type {\n ByDestinationNodeIdExperienceOptions,\n ByDestinationPathExperienceOptions,\n ByIdExperienceOptions,\n ClientOptions,\n CreateClientOptions,\n DestinationRedirectResult,\n ExperienceOptions,\n ExperienceResponse,\n ResolveOptions,\n} from '@contentful/experiences-client';\n"],"mappings":"AAeA;AAAA,EAC8B;AAAA,EAC5B,4BAAAA;AAAA,OACK;AAIP,SAAS,gCAAgC;AAGzC,SAAS,wBAAwB;AAGjC,SAAS,sBAAsB;AAG/B,SAAS,uBAAuB;AAKhC,SAAS,gCAAgC;AAKzC,SAAS,sBAAsB;AAG/B,SAAS,yBAAyB;AAclC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP,SAAS,eAAe,wBAAwB,uCAAuC;AACvF,SAAS,uBAAuB;AAChC,SAAS,aAAa;AAItB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAgBP,SAAS,yBAAyB;AAIlC,SAAS,yBAAyB,oBAAoB;AAmCtD,SAAS,gBAAgB,eAAe,gBAAgB;AAGxD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":["ClientExperienceRenderer"]}
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { PortableRenderNode, ViewportDef } from '@contentful/experiences-sdk-core';
2
+ import { PortableRenderNode } from '@contentful/experiences-sdk-core';
3
3
  import { ComponentErrorProps } from './component-error.js';
4
4
  import { MissingComponentProps } from './missing-component.js';
5
5
  import { Config } from './types.js';
@@ -18,14 +18,10 @@ type DiagnosticReporter = (error: Error) => void;
18
18
  interface NodesRendererProps {
19
19
  nodes: PortableRenderNode[];
20
20
  config: Config;
21
- viewports: ViewportDef[];
22
- activeViewportIndex: number;
23
- /** Viewport index the server pre-resolved design against. */
24
- fallbackViewportIndex: number;
25
21
  renderUnknown: RenderUnknown;
26
22
  renderError: RenderError;
27
23
  onDiagnostic: DiagnosticReporter;
28
24
  }
29
- declare function NodesRenderer({ nodes, config, viewports, activeViewportIndex, fallbackViewportIndex, renderUnknown, renderError, onDiagnostic, }: NodesRendererProps): ReactNode;
25
+ declare function NodesRenderer({ nodes, config, renderUnknown, renderError, onDiagnostic, }: NodesRendererProps): ReactNode;
30
26
 
31
27
  export { type DiagnosticReporter, NodesRenderer, type NodesRendererProps, type RenderError, type RenderUnknown };
@@ -1,6 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { Fragment, createElement } from "react";
3
- import { selectResolvedDesign } from "@contentful/experiences-design";
4
3
  import { ComponentErrorBoundary } from "./component-error-boundary";
5
4
  import {
6
5
  ContentfulComponentProvider,
@@ -14,9 +13,6 @@ import {
14
13
  function NodesRenderer({
15
14
  nodes,
16
15
  config,
17
- viewports,
18
- activeViewportIndex,
19
- fallbackViewportIndex,
20
16
  renderUnknown,
21
17
  renderError,
22
18
  onDiagnostic
@@ -27,9 +23,6 @@ function NodesRenderer({
27
23
  {
28
24
  node,
29
25
  config,
30
- viewports,
31
- activeViewportIndex,
32
- fallbackViewportIndex,
33
26
  renderUnknown,
34
27
  renderError,
35
28
  onDiagnostic
@@ -40,9 +33,6 @@ function NodesRenderer({
40
33
  function NodeRenderer({
41
34
  node,
42
35
  config,
43
- viewports,
44
- activeViewportIndex,
45
- fallbackViewportIndex,
46
36
  renderUnknown,
47
37
  renderError,
48
38
  onDiagnostic
@@ -65,9 +55,6 @@ function NodeRenderer({
65
55
  {
66
56
  node: child,
67
57
  config,
68
- viewports,
69
- activeViewportIndex,
70
- fallbackViewportIndex,
71
58
  renderUnknown,
72
59
  renderError,
73
60
  onDiagnostic
@@ -93,18 +80,7 @@ function NodeRenderer({
93
80
  return createElement(renderUnknown, { componentId: id, nodeId: node.nodeId });
94
81
  }
95
82
  const registrationConfig = isExperienceTemplate ? normalizeExperienceTemplateRegistration(entry) : normalizeComponentRegistration(entry);
96
- const { props: tokenResolvedDesign, unresolved } = selectResolvedDesign(
97
- node.props,
98
- viewports,
99
- activeViewportIndex,
100
- fallbackViewportIndex,
101
- config.resolveToken
102
- );
103
- if (unresolved.length && typeof console !== "undefined") {
104
- console.warn(
105
- `[@contentful/experiences-react] resolveToken returned undefined for token id(s) on ${kind} "${id}": ${unresolved.join(", ")}. useDesignValues() will omit those keys.`
106
- );
107
- }
83
+ const tokenResolvedDesign = node.props.design;
108
84
  const composed = {
109
85
  ...registrationConfig.defaults,
110
86
  ...tokenResolvedDesign,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/nodes-renderer.tsx"],"sourcesContent":["/*\n * Recursive renderer over PortableRenderNodes. Resolved design values auto-fill\n * matching props (below content/resolveData) and are also available via\n * `useDesignValues()`.\n */\n\nimport { Fragment, createElement, type ReactNode } from 'react';\n\nimport type { PortableRenderNode, ViewportDef } from '@contentful/experiences-sdk-core';\nimport { selectResolvedDesign } from '@contentful/experiences-design';\n\nimport { ComponentErrorBoundary } from './component-error-boundary';\nimport type { ComponentErrorProps } from './component-error';\nimport {\n ContentfulComponentProvider,\n ContentfulExperienceTemplateProvider,\n ResolvedDesignProvider,\n} from './context';\nimport type { MissingComponentProps } from './missing-component';\nimport {\n normalizeComponentRegistration,\n normalizeExperienceTemplateRegistration,\n type ContentfulComponent,\n type ContentfulExperienceTemplate,\n type Config,\n} from './types';\n\nexport type RenderUnknown = (props: MissingComponentProps) => ReactNode;\nexport type RenderError = (props: ComponentErrorProps) => ReactNode;\n\n/**\n * Reports one render-time diagnostic (unregistered id, a component that\n * threw). `ServerExperienceRenderer` passes a closure that pushes onto a\n * plain array (SSR is synchronous top-down, so the array is fully populated\n * by the time `<DebugExperience>` reads it — see the element-order note\n * there). `ClientExperienceRenderer` passes a `setState` updater instead, so\n * `<DebugExperience>` re-renders reactively when a later interaction throws.\n */\nexport type DiagnosticReporter = (error: Error) => void;\n\n// Internal renderers take `viewports` + `activeViewportIndex`, not the whole\n// RenderContext object — the context is published once via ExperienceProvider,\n// and re-threading it as an element prop makes React's RSC serializer back-patch\n// a shared reference into frozen props (\"Cannot assign to read only property\").\nexport interface NodesRendererProps {\n nodes: PortableRenderNode[];\n config: Config;\n viewports: ViewportDef[];\n activeViewportIndex: number;\n /** Viewport index the server pre-resolved design against. */\n fallbackViewportIndex: number;\n renderUnknown: RenderUnknown;\n renderError: RenderError;\n onDiagnostic: DiagnosticReporter;\n}\n\nexport function NodesRenderer({\n nodes,\n config,\n viewports,\n activeViewportIndex,\n fallbackViewportIndex,\n renderUnknown,\n renderError,\n onDiagnostic,\n}: NodesRendererProps): ReactNode {\n if (!nodes.length) return null;\n return (\n <Fragment>\n {nodes.map((node, index) => (\n <NodeRenderer\n key={node.nodeId ?? index}\n node={node}\n config={config}\n viewports={viewports}\n activeViewportIndex={activeViewportIndex}\n fallbackViewportIndex={fallbackViewportIndex}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n ))}\n </Fragment>\n );\n}\n\ninterface NodeRendererProps {\n node: PortableRenderNode;\n config: Config;\n viewports: ViewportDef[];\n activeViewportIndex: number;\n fallbackViewportIndex: number;\n renderUnknown: RenderUnknown;\n renderError: RenderError;\n onDiagnostic: DiagnosticReporter;\n}\n\nfunction NodeRenderer({\n node,\n config,\n viewports,\n activeViewportIndex,\n fallbackViewportIndex,\n renderUnknown,\n renderError,\n onDiagnostic,\n}: NodeRendererProps): ReactNode {\n const { kind, id } = node.registration;\n const isExperienceTemplate = kind === 'experienceTemplate';\n\n // Diagnostics are reported straight to `onDiagnostic`, synchronously during\n // render. Deliberate: this component is shared between SSR and CSR, and\n // `useEffect` never runs during `renderToStaticMarkup`/streaming SSR — an\n // effect-deferred report would silently vanish on the server.\n //\n // Dedup lives in `onDiagnostic` (see both renderers), not here. An ancestor\n // re-render re-executes this whole function, so an unregistered id or\n // malformed slot would otherwise be re-reported every time — the collectors\n // drop a message they already hold. Keeping the guard there rather than in a\n // `useRef` is what lets this module stay hook-free, and therefore usable from\n // a React Server Component: `ServerExperienceRenderer` renders it without a\n // `'use client'` boundary, which is what keeps `config` (component\n // references, unserializable) from having to cross one.\n\n // Pre-render each slot's children as an *array* of ReactNodes (one keyed\n // element per child), not a single wrapping node. A component can drop the\n // array straight into JSX for the common \"just render them\" case (React\n // renders keyed arrays), or map/filter/wrap the children individually.\n //\n // Slot name → prop name, for both node kinds: a coded Experience Template\n // declaring a `content` slot receives a `content` prop. `children` is not a\n // special case, just the conventional default slot name.\n const slotProps: Record<string, ReactNode[]> = {};\n for (const [slotName, children] of Object.entries(node.slots)) {\n // Defensive: `node.slots[x]` is typed as an array, but a hand-built\n // PortableRenderPlan (a supported path — customers can construct one\n // directly instead of going through `resolveExperience`) is not\n // type-checked at runtime. Warn + drop rather than letting `.map` throw.\n if (!Array.isArray(children)) {\n const message =\n `Slot \"${slotName}\" on ${kind} \"${id}\"${node.nodeId ? ` (node \"${node.nodeId}\")` : ''} ` +\n `is not an array of nodes; rendering it as empty instead of throwing.`;\n if (typeof console !== 'undefined') {\n console.warn(`[@contentful/experiences-react] ${message}`);\n }\n onDiagnostic(new Error(message));\n slotProps[slotName] = [];\n continue;\n }\n slotProps[slotName] = children.map((child, index) => (\n <NodeRenderer\n key={child.nodeId ?? index}\n node={child}\n config={config}\n viewports={viewports}\n activeViewportIndex={activeViewportIndex}\n fallbackViewportIndex={fallbackViewportIndex}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n ));\n }\n\n const entry = isExperienceTemplate ? config.experienceTemplates?.[id] : config.components[id];\n if (!entry) {\n // An unregistered Experience Template would blank the page if we swapped\n // it for the missing-component box, so warn and render its slot children\n // unwrapped — the content survives, the diagnostic names what's missing.\n if (isExperienceTemplate) {\n const message = `No experience template registered for id \"${id}\". Rendering its slot children without the experience template wrapper.`;\n if (typeof console !== 'undefined') {\n console.warn(`[@contentful/experiences-react] ${message}`);\n }\n onDiagnostic(new Error(message));\n return <Fragment>{Object.values(slotProps).flat()}</Fragment>;\n }\n // `MissingComponent` (the default `renderUnknown`) does its own\n // console.warn; a custom override may not, so the diagnostic is recorded\n // here regardless of which fallback ends up rendering.\n onDiagnostic(\n new Error(\n `No component registered for id \"${id}\"${node.nodeId ? ` (nodeId: ${node.nodeId})` : ''}.`\n )\n );\n return createElement(renderUnknown, { componentId: id, nodeId: node.nodeId });\n }\n const registrationConfig = isExperienceTemplate\n ? normalizeExperienceTemplateRegistration(entry)\n : normalizeComponentRegistration(entry);\n\n const { props: tokenResolvedDesign, unresolved } = selectResolvedDesign(\n node.props,\n viewports,\n activeViewportIndex,\n fallbackViewportIndex,\n config.resolveToken\n );\n if (unresolved.length && typeof console !== 'undefined') {\n console.warn(\n `[@contentful/experiences-react] resolveToken returned undefined for token id(s) on ${kind} \"${id}\": ${unresolved.join(', ')}. useDesignValues() will omit those keys.`\n );\n }\n\n // Merge precedence (last wins): defaults < design < content < resolveData < slots.\n const composed = {\n ...registrationConfig.defaults,\n ...tokenResolvedDesign,\n ...node.props.content,\n ...node.props.resolved,\n ...slotProps,\n };\n\n const element = (\n <ResolvedDesignProvider value={tokenResolvedDesign}>\n <ComponentErrorBoundary\n fallback={createElement(renderError, { componentId: id, nodeId: node.nodeId })}\n componentId={id}\n kind={kind}\n nodeId={node.nodeId}\n >\n {createElement(registrationConfig.component, composed)}\n </ComponentErrorBoundary>\n </ResolvedDesignProvider>\n );\n\n if (isExperienceTemplate) {\n const contentful: ContentfulExperienceTemplate = {\n experienceTemplateId: id,\n nodeId: node.nodeId,\n content: node.props.content,\n design: node.props.designRaw,\n resolved: node.props.resolved,\n };\n return (\n <ContentfulExperienceTemplateProvider value={contentful}>\n {element}\n </ContentfulExperienceTemplateProvider>\n );\n }\n\n const contentful: ContentfulComponent = {\n componentId: id,\n nodeId: node.nodeId,\n content: node.props.content,\n design: node.props.designRaw,\n resolved: node.props.resolved,\n };\n return <ContentfulComponentProvider value={contentful}>{element}</ContentfulComponentProvider>;\n}\n\n// `ComponentErrorBoundary` lives in its own `'use client'` file — see\n// component-error-boundary.tsx for why (it's a class component, which\n// Next.js's RSC bundler rejects from a directive-free file).\n"],"mappings":"AAsEQ;AAhER,SAAS,UAAU,qBAAqC;AAGxD,SAAS,4BAA4B;AAErC,SAAS,8BAA8B;AAEvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AA+BA,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkC;AAChC,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SACE,oBAAC,YACE,gBAAM,IAAI,CAAC,MAAM,UAChB;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,IARK,KAAK,UAAU;AAAA,EAStB,CACD,GACH;AAEJ;AAaA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiC;AAC/B,QAAM,EAAE,MAAM,GAAG,IAAI,KAAK;AAC1B,QAAM,uBAAuB,SAAS;AAwBtC,QAAM,YAAyC,CAAC;AAChD,aAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AAK7D,QAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;AAC5B,YAAM,UACJ,SAAS,QAAQ,QAAQ,IAAI,KAAK,EAAE,IAAI,KAAK,SAAS,WAAW,KAAK,MAAM,OAAO,EAAE;AAEvF,UAAI,OAAO,YAAY,aAAa;AAClC,gBAAQ,KAAK,mCAAmC,OAAO,EAAE;AAAA,MAC3D;AACA,mBAAa,IAAI,MAAM,OAAO,CAAC;AAC/B,gBAAU,QAAQ,IAAI,CAAC;AACvB;AAAA,IACF;AACA,cAAU,QAAQ,IAAI,SAAS,IAAI,CAAC,OAAO,UACzC;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,MARK,MAAM,UAAU;AAAA,IASvB,CACD;AAAA,EACH;AAEA,QAAM,QAAQ,uBAAuB,OAAO,sBAAsB,EAAE,IAAI,OAAO,WAAW,EAAE;AAC5F,MAAI,CAAC,OAAO;AAIV,QAAI,sBAAsB;AACxB,YAAM,UAAU,6CAA6C,EAAE;AAC/D,UAAI,OAAO,YAAY,aAAa;AAClC,gBAAQ,KAAK,mCAAmC,OAAO,EAAE;AAAA,MAC3D;AACA,mBAAa,IAAI,MAAM,OAAO,CAAC;AAC/B,aAAO,oBAAC,YAAU,iBAAO,OAAO,SAAS,EAAE,KAAK,GAAE;AAAA,IACpD;AAIA;AAAA,MACE,IAAI;AAAA,QACF,mCAAmC,EAAE,IAAI,KAAK,SAAS,aAAa,KAAK,MAAM,MAAM,EAAE;AAAA,MACzF;AAAA,IACF;AACA,WAAO,cAAc,eAAe,EAAE,aAAa,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,EAC9E;AACA,QAAM,qBAAqB,uBACvB,wCAAwC,KAAK,IAC7C,+BAA+B,KAAK;AAExC,QAAM,EAAE,OAAO,qBAAqB,WAAW,IAAI;AAAA,IACjD,KAAK;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO;AAAA,EACT;AACA,MAAI,WAAW,UAAU,OAAO,YAAY,aAAa;AACvD,YAAQ;AAAA,MACN,sFAAsF,IAAI,KAAK,EAAE,MAAM,WAAW,KAAK,IAAI,CAAC;AAAA,IAC9H;AAAA,EACF;AAGA,QAAM,WAAW;AAAA,IACf,GAAG,mBAAmB;AAAA,IACtB,GAAG;AAAA,IACH,GAAG,KAAK,MAAM;AAAA,IACd,GAAG,KAAK,MAAM;AAAA,IACd,GAAG;AAAA,EACL;AAEA,QAAM,UACJ,oBAAC,0BAAuB,OAAO,qBAC7B;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,cAAc,aAAa,EAAE,aAAa,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,MAC7E,aAAa;AAAA,MACb;AAAA,MACA,QAAQ,KAAK;AAAA,MAEZ,wBAAc,mBAAmB,WAAW,QAAQ;AAAA;AAAA,EACvD,GACF;AAGF,MAAI,sBAAsB;AACxB,UAAMA,cAA2C;AAAA,MAC/C,sBAAsB;AAAA,MACtB,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK,MAAM;AAAA,MACpB,QAAQ,KAAK,MAAM;AAAA,MACnB,UAAU,KAAK,MAAM;AAAA,IACvB;AACA,WACE,oBAAC,wCAAqC,OAAOA,aAC1C,mBACH;AAAA,EAEJ;AAEA,QAAM,aAAkC;AAAA,IACtC,aAAa;AAAA,IACb,QAAQ,KAAK;AAAA,IACb,SAAS,KAAK,MAAM;AAAA,IACpB,QAAQ,KAAK,MAAM;AAAA,IACnB,UAAU,KAAK,MAAM;AAAA,EACvB;AACA,SAAO,oBAAC,+BAA4B,OAAO,YAAa,mBAAQ;AAClE;","names":["contentful"]}
1
+ {"version":3,"sources":["../src/nodes-renderer.tsx"],"sourcesContent":["/*\n * Recursive renderer over PortableRenderNodes. Resolved design values auto-fill\n * matching props (below content/resolveData) and are also available via\n * `useDesignValues()`.\n */\n\nimport { Fragment, createElement, type ReactNode } from 'react';\n\nimport type { PortableRenderNode } from '@contentful/experiences-sdk-core';\n\nimport { ComponentErrorBoundary } from './component-error-boundary';\nimport type { ComponentErrorProps } from './component-error';\nimport {\n ContentfulComponentProvider,\n ContentfulExperienceTemplateProvider,\n ResolvedDesignProvider,\n} from './context';\nimport type { MissingComponentProps } from './missing-component';\nimport {\n normalizeComponentRegistration,\n normalizeExperienceTemplateRegistration,\n type ContentfulComponent,\n type ContentfulExperienceTemplate,\n type Config,\n} from './types';\n\nexport type RenderUnknown = (props: MissingComponentProps) => ReactNode;\nexport type RenderError = (props: ComponentErrorProps) => ReactNode;\n\n/**\n * Reports one render-time diagnostic (unregistered id, a component that\n * threw). `ServerExperienceRenderer` passes a closure that pushes onto a\n * plain array (SSR is synchronous top-down, so the array is fully populated\n * by the time `<DebugExperience>` reads it — see the element-order note\n * there). `ClientExperienceRenderer` passes a `setState` updater instead, so\n * `<DebugExperience>` re-renders reactively when a later interaction throws.\n */\nexport type DiagnosticReporter = (error: Error) => void;\n\nexport interface NodesRendererProps {\n nodes: PortableRenderNode[];\n config: Config;\n renderUnknown: RenderUnknown;\n renderError: RenderError;\n onDiagnostic: DiagnosticReporter;\n}\n\nexport function NodesRenderer({\n nodes,\n config,\n renderUnknown,\n renderError,\n onDiagnostic,\n}: NodesRendererProps): ReactNode {\n if (!nodes.length) return null;\n return (\n <Fragment>\n {nodes.map((node, index) => (\n <NodeRenderer\n key={node.nodeId ?? index}\n node={node}\n config={config}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n ))}\n </Fragment>\n );\n}\n\ninterface NodeRendererProps {\n node: PortableRenderNode;\n config: Config;\n renderUnknown: RenderUnknown;\n renderError: RenderError;\n onDiagnostic: DiagnosticReporter;\n}\n\nfunction NodeRenderer({\n node,\n config,\n renderUnknown,\n renderError,\n onDiagnostic,\n}: NodeRendererProps): ReactNode {\n const { kind, id } = node.registration;\n const isExperienceTemplate = kind === 'experienceTemplate';\n\n // Diagnostics are reported straight to `onDiagnostic`, synchronously during\n // render. Deliberate: this component is shared between SSR and CSR, and\n // `useEffect` never runs during `renderToStaticMarkup`/streaming SSR — an\n // effect-deferred report would silently vanish on the server.\n //\n // Dedup lives in `onDiagnostic` (see both renderers), not here. An ancestor\n // re-render re-executes this whole function, so an unregistered id or\n // malformed slot would otherwise be re-reported every time — the collectors\n // drop a message they already hold. Keeping the guard there rather than in a\n // `useRef` is what lets this module stay hook-free, and therefore usable from\n // a React Server Component: `ServerExperienceRenderer` renders it without a\n // `'use client'` boundary, which is what keeps `config` (component\n // references, unserializable) from having to cross one.\n\n // Pre-render each slot's children as an *array* of ReactNodes (one keyed\n // element per child), not a single wrapping node. A component can drop the\n // array straight into JSX for the common \"just render them\" case (React\n // renders keyed arrays), or map/filter/wrap the children individually.\n //\n // Slot name → prop name, for both node kinds: a coded Experience Template\n // declaring a `content` slot receives a `content` prop. `children` is not a\n // special case, just the conventional default slot name.\n const slotProps: Record<string, ReactNode[]> = {};\n for (const [slotName, children] of Object.entries(node.slots)) {\n // Defensive: `node.slots[x]` is typed as an array, but a hand-built\n // PortableRenderPlan (a supported path — customers can construct one\n // directly instead of going through `resolveExperience`) is not\n // type-checked at runtime. Warn + drop rather than letting `.map` throw.\n if (!Array.isArray(children)) {\n const message =\n `Slot \"${slotName}\" on ${kind} \"${id}\"${node.nodeId ? ` (node \"${node.nodeId}\")` : ''} ` +\n `is not an array of nodes; rendering it as empty instead of throwing.`;\n if (typeof console !== 'undefined') {\n console.warn(`[@contentful/experiences-react] ${message}`);\n }\n onDiagnostic(new Error(message));\n slotProps[slotName] = [];\n continue;\n }\n slotProps[slotName] = children.map((child, index) => (\n <NodeRenderer\n key={child.nodeId ?? index}\n node={child}\n config={config}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n ));\n }\n\n const entry = isExperienceTemplate ? config.experienceTemplates?.[id] : config.components[id];\n if (!entry) {\n // An unregistered Experience Template would blank the page if we swapped\n // it for the missing-component box, so warn and render its slot children\n // unwrapped — the content survives, the diagnostic names what's missing.\n if (isExperienceTemplate) {\n const message = `No experience template registered for id \"${id}\". Rendering its slot children without the experience template wrapper.`;\n if (typeof console !== 'undefined') {\n console.warn(`[@contentful/experiences-react] ${message}`);\n }\n onDiagnostic(new Error(message));\n return <Fragment>{Object.values(slotProps).flat()}</Fragment>;\n }\n // `MissingComponent` (the default `renderUnknown`) does its own\n // console.warn; a custom override may not, so the diagnostic is recorded\n // here regardless of which fallback ends up rendering.\n onDiagnostic(\n new Error(\n `No component registered for id \"${id}\"${node.nodeId ? ` (nodeId: ${node.nodeId})` : ''}.`\n )\n );\n return createElement(renderUnknown, { componentId: id, nodeId: node.nodeId });\n }\n const registrationConfig = isExperienceTemplate\n ? normalizeExperienceTemplateRegistration(entry)\n : normalizeComponentRegistration(entry);\n\n const tokenResolvedDesign = node.props.design;\n\n // Merge precedence (last wins): defaults < design < content < resolveData < slots.\n const composed = {\n ...registrationConfig.defaults,\n ...tokenResolvedDesign,\n ...node.props.content,\n ...node.props.resolved,\n ...slotProps,\n };\n\n const element = (\n <ResolvedDesignProvider value={tokenResolvedDesign}>\n <ComponentErrorBoundary\n fallback={createElement(renderError, { componentId: id, nodeId: node.nodeId })}\n componentId={id}\n kind={kind}\n nodeId={node.nodeId}\n >\n {createElement(registrationConfig.component, composed)}\n </ComponentErrorBoundary>\n </ResolvedDesignProvider>\n );\n\n if (isExperienceTemplate) {\n const contentful: ContentfulExperienceTemplate = {\n experienceTemplateId: id,\n nodeId: node.nodeId,\n content: node.props.content,\n design: node.props.designRaw,\n resolved: node.props.resolved,\n };\n return (\n <ContentfulExperienceTemplateProvider value={contentful}>\n {element}\n </ContentfulExperienceTemplateProvider>\n );\n }\n\n const contentful: ContentfulComponent = {\n componentId: id,\n nodeId: node.nodeId,\n content: node.props.content,\n design: node.props.designRaw,\n resolved: node.props.resolved,\n };\n return <ContentfulComponentProvider value={contentful}>{element}</ContentfulComponentProvider>;\n}\n\n// `ComponentErrorBoundary` lives in its own `'use client'` file — see\n// component-error-boundary.tsx for why (it's a class component, which\n// Next.js's RSC bundler rejects from a directive-free file).\n"],"mappings":"AA0DQ;AApDR,SAAS,UAAU,qBAAqC;AAIxD,SAAS,8BAA8B;AAEvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAuBA,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkC;AAChC,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SACE,oBAAC,YACE,gBAAM,IAAI,CAAC,MAAM,UAChB;AAAA,IAAC;AAAA;AAAA,MAEC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,IALK,KAAK,UAAU;AAAA,EAMtB,CACD,GACH;AAEJ;AAUA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiC;AAC/B,QAAM,EAAE,MAAM,GAAG,IAAI,KAAK;AAC1B,QAAM,uBAAuB,SAAS;AAwBtC,QAAM,YAAyC,CAAC;AAChD,aAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AAK7D,QAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG;AAC5B,YAAM,UACJ,SAAS,QAAQ,QAAQ,IAAI,KAAK,EAAE,IAAI,KAAK,SAAS,WAAW,KAAK,MAAM,OAAO,EAAE;AAEvF,UAAI,OAAO,YAAY,aAAa;AAClC,gBAAQ,KAAK,mCAAmC,OAAO,EAAE;AAAA,MAC3D;AACA,mBAAa,IAAI,MAAM,OAAO,CAAC;AAC/B,gBAAU,QAAQ,IAAI,CAAC;AACvB;AAAA,IACF;AACA,cAAU,QAAQ,IAAI,SAAS,IAAI,CAAC,OAAO,UACzC;AAAA,MAAC;AAAA;AAAA,QAEC,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,MALK,MAAM,UAAU;AAAA,IAMvB,CACD;AAAA,EACH;AAEA,QAAM,QAAQ,uBAAuB,OAAO,sBAAsB,EAAE,IAAI,OAAO,WAAW,EAAE;AAC5F,MAAI,CAAC,OAAO;AAIV,QAAI,sBAAsB;AACxB,YAAM,UAAU,6CAA6C,EAAE;AAC/D,UAAI,OAAO,YAAY,aAAa;AAClC,gBAAQ,KAAK,mCAAmC,OAAO,EAAE;AAAA,MAC3D;AACA,mBAAa,IAAI,MAAM,OAAO,CAAC;AAC/B,aAAO,oBAAC,YAAU,iBAAO,OAAO,SAAS,EAAE,KAAK,GAAE;AAAA,IACpD;AAIA;AAAA,MACE,IAAI;AAAA,QACF,mCAAmC,EAAE,IAAI,KAAK,SAAS,aAAa,KAAK,MAAM,MAAM,EAAE;AAAA,MACzF;AAAA,IACF;AACA,WAAO,cAAc,eAAe,EAAE,aAAa,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,EAC9E;AACA,QAAM,qBAAqB,uBACvB,wCAAwC,KAAK,IAC7C,+BAA+B,KAAK;AAExC,QAAM,sBAAsB,KAAK,MAAM;AAGvC,QAAM,WAAW;AAAA,IACf,GAAG,mBAAmB;AAAA,IACtB,GAAG;AAAA,IACH,GAAG,KAAK,MAAM;AAAA,IACd,GAAG,KAAK,MAAM;AAAA,IACd,GAAG;AAAA,EACL;AAEA,QAAM,UACJ,oBAAC,0BAAuB,OAAO,qBAC7B;AAAA,IAAC;AAAA;AAAA,MACC,UAAU,cAAc,aAAa,EAAE,aAAa,IAAI,QAAQ,KAAK,OAAO,CAAC;AAAA,MAC7E,aAAa;AAAA,MACb;AAAA,MACA,QAAQ,KAAK;AAAA,MAEZ,wBAAc,mBAAmB,WAAW,QAAQ;AAAA;AAAA,EACvD,GACF;AAGF,MAAI,sBAAsB;AACxB,UAAMA,cAA2C;AAAA,MAC/C,sBAAsB;AAAA,MACtB,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK,MAAM;AAAA,MACpB,QAAQ,KAAK,MAAM;AAAA,MACnB,UAAU,KAAK,MAAM;AAAA,IACvB;AACA,WACE,oBAAC,wCAAqC,OAAOA,aAC1C,mBACH;AAAA,EAEJ;AAEA,QAAM,aAAkC;AAAA,IACtC,aAAa;AAAA,IACb,QAAQ,KAAK;AAAA,IACb,SAAS,KAAK,MAAM;AAAA,IACpB,QAAQ,KAAK,MAAM;AAAA,IACnB,UAAU,KAAK,MAAM;AAAA,EACvB;AACA,SAAO,oBAAC,+BAA4B,OAAO,YAAa,mBAAQ;AAClE;","names":["contentful"]}
@@ -8,11 +8,6 @@ import './missing-component.js';
8
8
  interface ServerExperienceRendererProps {
9
9
  experience: PortableRenderPlan | null | undefined;
10
10
  config: Config;
11
- /**
12
- * Viewport to render for, typically derived from the request's User-Agent.
13
- * Defaults to the viewport the plan was pre-resolved against.
14
- */
15
- initialViewportId?: string;
16
11
  /** Shallow-merges over the plan's `metadata`. Only needed to override it. */
17
12
  metadata?: Record<string, unknown>;
18
13
  /**
@@ -27,6 +22,6 @@ interface ServerExperienceRendererProps {
27
22
  /** Override the fallback rendered when a registered component throws. */
28
23
  renderError?: RenderError;
29
24
  }
30
- declare function ServerExperienceRenderer({ experience, config, initialViewportId, metadata, debug, renderUnknown, renderError, }: ServerExperienceRendererProps): ReactNode;
25
+ declare function ServerExperienceRenderer({ experience, config, metadata, debug, renderUnknown, renderError, }: ServerExperienceRendererProps): ReactNode;
31
26
 
32
27
  export { ServerExperienceRenderer, type ServerExperienceRendererProps };
@@ -1,5 +1,4 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { getViewportIndex } from "@contentful/experiences-design";
3
2
  import { ComponentError } from "./component-error";
4
3
  import { DebugExperience } from "./debug-experience";
5
4
  import { ExperienceProvider } from "./context";
@@ -7,19 +6,11 @@ import { MissingComponent } from "./missing-component";
7
6
  import { NodesRenderer } from "./nodes-renderer";
8
7
  const DEFAULT_CONTEXT = {
9
8
  debug: false,
10
- metadata: {},
11
- viewports: []
12
- };
13
- const FALLBACK_VIEWPORT = {
14
- id: "_",
15
- query: "*",
16
- displayName: "Default",
17
- previewSize: "100%"
9
+ metadata: {}
18
10
  };
19
11
  function ServerExperienceRenderer({
20
12
  experience,
21
13
  config,
22
- initialViewportId,
23
14
  metadata,
24
15
  debug,
25
16
  renderUnknown = MissingComponent,
@@ -27,17 +18,10 @@ function ServerExperienceRenderer({
27
18
  }) {
28
19
  if (!experience) return null;
29
20
  const resolvedDebug = debug ?? experience.debug;
30
- const activeViewportIndex = initialViewportId === void 0 ? experience.fallbackViewportIndex : getViewportIndex(experience.viewports, initialViewportId);
31
- const contextViewports = experience.viewports.map((v) => ({ ...v }));
32
- const activeViewport = { ...experience.viewports[activeViewportIndex] ?? FALLBACK_VIEWPORT };
33
21
  const renderContext = {
34
22
  ...DEFAULT_CONTEXT,
35
23
  debug: resolvedDebug,
36
- metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...metadata ?? {} },
37
- viewports: contextViewports,
38
- activeViewport,
39
- activeViewportIndex,
40
- fallbackViewportIndex: experience.fallbackViewportIndex
24
+ metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...metadata ?? {} }
41
25
  };
42
26
  const renderDiagnostics = [...experience.diagnostics ?? []];
43
27
  const seenDiagnostics = new Set(renderDiagnostics.map((error) => error.message));
@@ -51,9 +35,6 @@ function ServerExperienceRenderer({
51
35
  {
52
36
  nodes: experience.nodes,
53
37
  config,
54
- viewports: experience.viewports,
55
- activeViewportIndex,
56
- fallbackViewportIndex: experience.fallbackViewportIndex,
57
38
  renderUnknown,
58
39
  renderError,
59
40
  onDiagnostic
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server-renderer.tsx"],"sourcesContent":["/*\n * Server-safe Experience renderer. Resolves the active viewport once from\n * `initialViewportId` and renders without any reactive subscription.\n */\n\nimport type { ReactNode } from 'react';\n\nimport type {\n ExperienceContext,\n PortableRenderPlan,\n ViewportDef,\n} from '@contentful/experiences-sdk-core';\nimport { getViewportIndex } from '@contentful/experiences-design';\n\nimport { ComponentError } from './component-error';\nimport { DebugExperience } from './debug-experience';\nimport { ExperienceProvider } from './context';\nimport { MissingComponent } from './missing-component';\nimport { NodesRenderer, type RenderError, type RenderUnknown } from './nodes-renderer';\nimport type { Config, RenderContext } from './types';\n\nconst DEFAULT_CONTEXT: ExperienceContext = {\n debug: false,\n metadata: {},\n viewports: [],\n};\n\n// Keeps `activeViewport` non-null when a payload declares no viewports.\nconst FALLBACK_VIEWPORT: ViewportDef = {\n id: '_',\n query: '*',\n displayName: 'Default',\n previewSize: '100%',\n};\n\nexport interface ServerExperienceRendererProps {\n experience: PortableRenderPlan | null | undefined;\n config: Config;\n /**\n * Viewport to render for, typically derived from the request's User-Agent.\n * Defaults to the viewport the plan was pre-resolved against.\n */\n initialViewportId?: string;\n /** Shallow-merges over the plan's `metadata`. Only needed to override it. */\n metadata?: Record<string, unknown>;\n /**\n * Observability switch. When on: renders the visible missing-component box,\n * turns the default `renderUnknown` fallback into the debug component, and\n * auto-mounts `<DebugExperience>` (the resolved-plan JSON panel) after the\n * tree. Defaults to the plan's `debug`.\n */\n debug?: boolean;\n /** Override the fallback rendered for unregistered component types. */\n renderUnknown?: RenderUnknown;\n /** Override the fallback rendered when a registered component throws. */\n renderError?: RenderError;\n}\n\nexport function ServerExperienceRenderer({\n experience,\n config,\n initialViewportId,\n metadata,\n debug,\n renderUnknown = MissingComponent,\n renderError = ComponentError,\n}: ServerExperienceRendererProps): ReactNode {\n if (!experience) return null;\n\n // `??`, not `||`, so an explicit `debug={false}` overrides a debug-on plan.\n const resolvedDebug = debug ?? experience.debug;\n // Default to the pre-resolved viewport so first paint needs no recompute.\n const activeViewportIndex =\n initialViewportId === undefined\n ? experience.fallbackViewportIndex\n : getViewportIndex(experience.viewports, initialViewportId);\n\n // Copy viewports/activeViewport so the context (serialized + frozen by RSC)\n // shares no object identity with the plan arrays the renderers read below —\n // a shared reference makes Flight back-patch into frozen props and throw.\n const contextViewports = experience.viewports.map((v) => ({ ...v }));\n const activeViewport = { ...(experience.viewports[activeViewportIndex] ?? FALLBACK_VIEWPORT) };\n\n const renderContext: RenderContext = {\n ...DEFAULT_CONTEXT,\n debug: resolvedDebug,\n metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...(metadata ?? {}) },\n viewports: contextViewports,\n activeViewport,\n activeViewportIndex,\n fallbackViewportIndex: experience.fallbackViewportIndex,\n };\n\n // Render-time diagnostics (unregistered id, a component that threw),\n // collected into a plain array rather than React state: SSR is synchronous\n // top-down, so by the time `<DebugExperience>` renders — after the tree,\n // see the element-order note below — this array is already fully populated.\n // Seeded with the resolve-time diagnostics up front rather than spreading\n // them in later: `<DebugExperience>` below reads this array by reference\n // (`errors={renderDiagnostics}`), not a spread copy — a copy would be built\n // right here, before React ever renders `tree` and gets a chance to push\n // into it.\n const renderDiagnostics: Error[] = [...(experience.diagnostics ?? [])];\n // Dedup by message, the guard `NodeRenderer` used to hold in a `useRef`. It\n // lives here so that module stays hook-free and therefore server-renderable.\n // One pass on the server, so this mostly guards a node that reports the same\n // message from several slots.\n const seenDiagnostics = new Set(renderDiagnostics.map((error) => error.message));\n const onDiagnostic = (error: Error): void => {\n if (seenDiagnostics.has(error.message)) return;\n seenDiagnostics.add(error.message);\n renderDiagnostics.push(error);\n };\n\n const tree = (\n <NodesRenderer\n nodes={experience.nodes}\n config={config}\n viewports={experience.viewports}\n activeViewportIndex={activeViewportIndex}\n fallbackViewportIndex={experience.fallbackViewportIndex}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n );\n\n // The node tree renders BEFORE `<DebugExperience>` — required, not\n // stylistic. React's render is synchronous top-down; if DebugExperience\n // rendered first (as it used to), it would read `renderDiagnostics` before\n // any descendant had a chance to push into it, and always see it empty.\n return (\n <ExperienceProvider value={renderContext}>\n {tree}\n {resolvedDebug ? (\n <DebugExperience experience={experience} errors={renderDiagnostics} />\n ) : null}\n </ExperienceProvider>\n );\n}\n"],"mappings":"AAmHI,cAiBA,YAjBA;AAvGJ,SAAS,wBAAwB;AAEjC,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,qBAA2D;AAGpE,MAAM,kBAAqC;AAAA,EACzC,OAAO;AAAA,EACP,UAAU,CAAC;AAAA,EACX,WAAW,CAAC;AACd;AAGA,MAAM,oBAAiC;AAAA,EACrC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,aAAa;AAAA,EACb,aAAa;AACf;AAyBO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB,cAAc;AAChB,GAA6C;AAC3C,MAAI,CAAC,WAAY,QAAO;AAGxB,QAAM,gBAAgB,SAAS,WAAW;AAE1C,QAAM,sBACJ,sBAAsB,SAClB,WAAW,wBACX,iBAAiB,WAAW,WAAW,iBAAiB;AAK9D,QAAM,mBAAmB,WAAW,UAAU,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AACnE,QAAM,iBAAiB,EAAE,GAAI,WAAW,UAAU,mBAAmB,KAAK,kBAAmB;AAE7F,QAAM,gBAA+B;AAAA,IACnC,GAAG;AAAA,IACH,OAAO;AAAA,IACP,UAAU,EAAE,GAAG,gBAAgB,UAAU,GAAG,WAAW,UAAU,GAAI,YAAY,CAAC,EAAG;AAAA,IACrF,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA,uBAAuB,WAAW;AAAA,EACpC;AAWA,QAAM,oBAA6B,CAAC,GAAI,WAAW,eAAe,CAAC,CAAE;AAKrE,QAAM,kBAAkB,IAAI,IAAI,kBAAkB,IAAI,CAAC,UAAU,MAAM,OAAO,CAAC;AAC/E,QAAM,eAAe,CAAC,UAAuB;AAC3C,QAAI,gBAAgB,IAAI,MAAM,OAAO,EAAG;AACxC,oBAAgB,IAAI,MAAM,OAAO;AACjC,sBAAkB,KAAK,KAAK;AAAA,EAC9B;AAEA,QAAM,OACJ;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,WAAW;AAAA,MAClB;AAAA,MACA,WAAW,WAAW;AAAA,MACtB;AAAA,MACA,uBAAuB,WAAW;AAAA,MAClC;AAAA,MACA;AAAA,MACA;AAAA;AAAA,EACF;AAOF,SACE,qBAAC,sBAAmB,OAAO,eACxB;AAAA;AAAA,IACA,gBACC,oBAAC,mBAAgB,YAAwB,QAAQ,mBAAmB,IAClE;AAAA,KACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/server-renderer.tsx"],"sourcesContent":["/*\n * Server-safe Experience renderer. It renders the resolved plan directly.\n */\n\nimport type { ReactNode } from 'react';\n\nimport type { ExperienceContext, PortableRenderPlan } from '@contentful/experiences-sdk-core';\n\nimport { ComponentError } from './component-error';\nimport { DebugExperience } from './debug-experience';\nimport { ExperienceProvider } from './context';\nimport { MissingComponent } from './missing-component';\nimport { NodesRenderer, type RenderError, type RenderUnknown } from './nodes-renderer';\nimport type { Config, RenderContext } from './types';\n\nconst DEFAULT_CONTEXT: ExperienceContext = {\n debug: false,\n metadata: {},\n};\n\nexport interface ServerExperienceRendererProps {\n experience: PortableRenderPlan | null | undefined;\n config: Config;\n /** Shallow-merges over the plan's `metadata`. Only needed to override it. */\n metadata?: Record<string, unknown>;\n /**\n * Observability switch. When on: renders the visible missing-component box,\n * turns the default `renderUnknown` fallback into the debug component, and\n * auto-mounts `<DebugExperience>` (the resolved-plan JSON panel) after the\n * tree. Defaults to the plan's `debug`.\n */\n debug?: boolean;\n /** Override the fallback rendered for unregistered component types. */\n renderUnknown?: RenderUnknown;\n /** Override the fallback rendered when a registered component throws. */\n renderError?: RenderError;\n}\n\nexport function ServerExperienceRenderer({\n experience,\n config,\n metadata,\n debug,\n renderUnknown = MissingComponent,\n renderError = ComponentError,\n}: ServerExperienceRendererProps): ReactNode {\n if (!experience) return null;\n\n // `??`, not `||`, so an explicit `debug={false}` overrides a debug-on plan.\n const resolvedDebug = debug ?? experience.debug;\n const renderContext: RenderContext = {\n ...DEFAULT_CONTEXT,\n debug: resolvedDebug,\n metadata: { ...DEFAULT_CONTEXT.metadata, ...experience.metadata, ...(metadata ?? {}) },\n };\n\n // Render-time diagnostics (unregistered id, a component that threw),\n // collected into a plain array rather than React state: SSR is synchronous\n // top-down, so by the time `<DebugExperience>` renders — after the tree,\n // see the element-order note below — this array is already fully populated.\n // Seeded with the resolve-time diagnostics up front rather than spreading\n // them in later: `<DebugExperience>` below reads this array by reference\n // (`errors={renderDiagnostics}`), not a spread copy — a copy would be built\n // right here, before React ever renders `tree` and gets a chance to push\n // into it.\n const renderDiagnostics: Error[] = [...(experience.diagnostics ?? [])];\n // Dedup by message, the guard `NodeRenderer` used to hold in a `useRef`. It\n // lives here so that module stays hook-free and therefore server-renderable.\n // One pass on the server, so this mostly guards a node that reports the same\n // message from several slots.\n const seenDiagnostics = new Set(renderDiagnostics.map((error) => error.message));\n const onDiagnostic = (error: Error): void => {\n if (seenDiagnostics.has(error.message)) return;\n seenDiagnostics.add(error.message);\n renderDiagnostics.push(error);\n };\n\n const tree = (\n <NodesRenderer\n nodes={experience.nodes}\n config={config}\n renderUnknown={renderUnknown}\n renderError={renderError}\n onDiagnostic={onDiagnostic}\n />\n );\n\n // The node tree renders BEFORE `<DebugExperience>` — required, not\n // stylistic. React's render is synchronous top-down; if DebugExperience\n // rendered first (as it used to), it would read `renderDiagnostics` before\n // any descendant had a chance to push into it, and always see it empty.\n return (\n <ExperienceProvider value={renderContext}>\n {tree}\n {resolvedDebug ? (\n <DebugExperience experience={experience} errors={renderDiagnostics} />\n ) : null}\n </ExperienceProvider>\n );\n}\n"],"mappings":"AA8EI,cAcA,YAdA;AAtEJ,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,0BAA0B;AACnC,SAAS,wBAAwB;AACjC,SAAS,qBAA2D;AAGpE,MAAM,kBAAqC;AAAA,EACzC,OAAO;AAAA,EACP,UAAU,CAAC;AACb;AAoBO,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB,cAAc;AAChB,GAA6C;AAC3C,MAAI,CAAC,WAAY,QAAO;AAGxB,QAAM,gBAAgB,SAAS,WAAW;AAC1C,QAAM,gBAA+B;AAAA,IACnC,GAAG;AAAA,IACH,OAAO;AAAA,IACP,UAAU,EAAE,GAAG,gBAAgB,UAAU,GAAG,WAAW,UAAU,GAAI,YAAY,CAAC,EAAG;AAAA,EACvF;AAWA,QAAM,oBAA6B,CAAC,GAAI,WAAW,eAAe,CAAC,CAAE;AAKrE,QAAM,kBAAkB,IAAI,IAAI,kBAAkB,IAAI,CAAC,UAAU,MAAM,OAAO,CAAC;AAC/E,QAAM,eAAe,CAAC,UAAuB;AAC3C,QAAI,gBAAgB,IAAI,MAAM,OAAO,EAAG;AACxC,oBAAgB,IAAI,MAAM,OAAO;AACjC,sBAAkB,KAAK,KAAK;AAAA,EAC9B;AAEA,QAAM,OACJ;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,WAAW;AAAA,MAClB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,EACF;AAOF,SACE,qBAAC,sBAAmB,OAAO,eACxB;AAAA;AAAA,IACA,gBACC,oBAAC,mBAAgB,YAAwB,QAAQ,mBAAmB,IAClE;AAAA,KACN;AAEJ;","names":[]}
package/dist/types.d.ts CHANGED
@@ -10,8 +10,8 @@ export { RenderContext, ResolveContext, ResolveToken } from '@contentful/experie
10
10
  * analytics on `nodeId`, or rendering a raw-payload panel in preview.
11
11
  *
12
12
  * Design properties stay in their **raw discriminated form** here (the same
13
- * shape `ctx.design` carries inside `resolveData`). The viewport-cascaded,
14
- * token-resolved values are what `useDesignValues()` returns.
13
+ * shape `ctx.design` carries inside `resolveData`). Token-resolved values are
14
+ * what `useDesignValues()` returns.
15
15
  */
16
16
  interface ContentfulComponent {
17
17
  componentId: string;
@@ -48,8 +48,7 @@ interface ComponentConfig<Props extends object = Record<string, unknown>> {
48
48
  defaults?: Partial<Props>;
49
49
  /**
50
50
  * Optional sync-or-async hook that derives final props from the raw
51
- * Experience inputs. Runs once during `resolveExperience`, before render —
52
- * does NOT re-fire on viewport changes.
51
+ * Experience inputs. Runs once during `resolveExperience`, before render.
53
52
  */
54
53
  resolveData?: (ctx: ResolveContext) => Partial<Props> | Promise<Partial<Props>>;
55
54
  /**
@@ -116,8 +115,8 @@ interface Config {
116
115
  experienceTemplates?: ExperienceTemplates;
117
116
  /**
118
117
  * Resolves `DesignToken` values to runtime values before they reach a
119
- * component. If omitted, they pass through unchanged. See `ResolveToken`
120
- * in `@contentful/experiences-sdk-core` for the full contract.
118
+ * component. Unresolved tokens remain raw and produce a diagnostic. See
119
+ * `ResolveToken` in `@contentful/experiences-sdk-core` for the full contract.
121
120
  */
122
121
  resolveToken?: ResolveToken;
123
122
  }
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { ComponentType } from 'react';\n\nimport type {\n DesignPropValue,\n RenderContext,\n ResolveContext,\n ResolveToken,\n} from '@contentful/experiences-sdk-core';\n\n/**\n * Re-exported for ergonomics: customer code authoring `resolveData` doesn't\n * have to dig into the core package for the context type.\n */\nexport type { ResolveContext, ResolveToken };\n\n/**\n * The full Contentful-side payload for a single component instance, exposed\n * via `useContentfulComponent()` to any descendant of a rendered Experience\n * node. Useful for custom design-property resolution outside the SDK's\n * cascade, branching by `componentId` in a generic wrapper, keying\n * analytics on `nodeId`, or rendering a raw-payload panel in preview.\n *\n * Design properties stay in their **raw discriminated form** here (the same\n * shape `ctx.design` carries inside `resolveData`). The viewport-cascaded,\n * token-resolved values are what `useDesignValues()` returns.\n */\nexport interface ContentfulComponent {\n componentId: string;\n nodeId?: string;\n content: Record<string, unknown>;\n design: Record<string, DesignPropValue>;\n resolved?: Record<string, unknown>;\n}\n\n/**\n * Same shape as `ContentfulComponent`, but for an Experience Template node.\n * Exposed via `useContentfulExperienceTemplate()` inside an Experience\n * Template's component tree.\n */\nexport interface ContentfulExperienceTemplate {\n experienceTemplateId: string;\n nodeId?: string;\n content: Record<string, unknown>;\n design: Record<string, DesignPropValue>;\n resolved?: Record<string, unknown>;\n}\n\n/** Render-time experience context, read with `useExperience()`. Declared in core so all adapters share one shape. */\nexport type { RenderContext };\n\n/**\n * Customer-supplied configuration for a single component. The `component`\n * receives the merged props (design + content + resolveData + slots); resolved\n * design values auto-fill matching props and are also readable via\n * `useDesignValues()`. Runtime context and raw payload come from\n * `useExperience()` / `useContentfulComponent()`.\n */\nexport interface ComponentConfig<Props extends object = Record<string, unknown>> {\n /**\n * Lowest-precedence defaults object. Merged in before content / resolveData /\n * slots. Useful for variant fallbacks the editorial layer doesn't always supply.\n */\n defaults?: Partial<Props>;\n /**\n * Optional sync-or-async hook that derives final props from the raw\n * Experience inputs. Runs once during `resolveExperience`, before render —\n * does NOT re-fire on viewport changes.\n */\n resolveData?: (ctx: ResolveContext) => Partial<Props> | Promise<Partial<Props>>;\n /**\n * The React component to render. Receives the merged props.\n */\n component: ComponentType<Props>;\n}\n\n/**\n * Registry value. Register a bare React component for the common case, or\n * the full `ComponentConfig` shape when you need defaults or a `resolveData`\n * hook.\n *\n * button: Button, // bare\n * header: { component: Header, defaults: {...} }, // with defaults\n * card: defineComponent<CardProps>({ component: Card, resolveData: ... }),\n */\nexport type Registration<Props extends object = Record<string, unknown>> =\n ComponentType<Props> | ComponentConfig<Props>;\n\n/**\n * Customer-supplied configuration for a coded Experience Template. Identical in\n * shape and behavior to `ComponentConfig` — an Experience Template is just a\n * node whose implementation lives in the `experienceTemplates` registry. Its\n * slots arrive as named props (a `content` slot becomes a `content` prop), so\n * there is no `children` special case.\n */\nexport interface ExperienceTemplateConfig<Props extends object = Record<string, unknown>> {\n /**\n * Lowest-precedence defaults object. Merged in before content / resolveData /\n * slots. Useful for variant fallbacks the editorial layer doesn't always supply.\n */\n defaults?: Partial<Props>;\n resolveData?: (ctx: ResolveContext) => Partial<Props> | Promise<Partial<Props>>;\n component: ComponentType<Props>;\n}\n\n/**\n * Registry value for Experience Templates. Same dual-shape as component\n * registrations.\n */\nexport type ExperienceTemplateRegistration<Props extends object = Record<string, unknown>> =\n ComponentType<Props> | ExperienceTemplateConfig<Props>;\n\n/**\n * Identity helper — returns the config as-is, but narrows the `resolveData`\n * and `component` parameter types to your declared `Props`.\n */\nexport function defineComponent<Props extends object = Record<string, unknown>>(\n config: ComponentConfig<Props>\n): ComponentConfig<Props> {\n return config;\n}\n\n/**\n * Identity helper — returns the Experience Template config as-is, with `Props`\n * narrowing.\n */\nexport function defineExperienceTemplate<Props extends object = Record<string, unknown>>(\n config: ExperienceTemplateConfig<Props>\n): ExperienceTemplateConfig<Props> {\n return config;\n}\n\n/**\n * Component registry — keyed by `componentId` (last slash-segment of\n * `component.sys.urn`). Per-entry prop narrowing happens at\n * `defineComponent<Props>` or at the bare-component's own type.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- per-entry narrowing happens at the registration author's call site.\nexport type Components = Record<string, Registration<any>>;\n\n/**\n * Experience Template registry — keyed by `experienceTemplateId` (last\n * slash-segment of an Experience Template node's own\n * `experienceTemplate.sys.urn`). Not read from `payload.sys`; templates are\n * ordinary nodes.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- per-entry narrowing happens at the registration author's call site.\nexport type ExperienceTemplates = Record<string, ExperienceTemplateRegistration<any>>;\n\nexport interface Config {\n components: Components;\n experienceTemplates?: ExperienceTemplates;\n /**\n * Resolves `DesignToken` values to runtime values before they reach a\n * component. If omitted, they pass through unchanged. See `ResolveToken`\n * in `@contentful/experiences-sdk-core` for the full contract.\n */\n resolveToken?: ResolveToken;\n}\n\n/**\n * Normalize a registry entry — bare component OR config object — into the\n * common `ComponentConfig` shape used by the renderer.\n *\n * React function components are plain functions; `React.memo` /\n * `React.forwardRef` yield objects carrying a `$$typeof` symbol. Both count\n * as bare components; a config object is discriminated by having a\n * `component` field and no `$$typeof`.\n */\nexport function normalizeComponentRegistration<P extends object>(\n reg: Registration<P>\n): ComponentConfig<P> {\n if (typeof reg === 'object' && reg !== null && !('$$typeof' in reg) && 'component' in reg) {\n return reg as ComponentConfig<P>;\n }\n return { component: reg as ComponentType<P> };\n}\n\nexport function normalizeExperienceTemplateRegistration<P extends object>(\n reg: ExperienceTemplateRegistration<P>\n): ExperienceTemplateConfig<P> {\n if (typeof reg === 'object' && reg !== null && !('$$typeof' in reg) && 'component' in reg) {\n return reg as ExperienceTemplateConfig<P>;\n }\n return { component: reg as ComponentType<P> };\n}\n"],"mappings":"AAmHO,SAAS,gBACd,QACwB;AACxB,SAAO;AACT;AAMO,SAAS,yBACd,QACiC;AACjC,SAAO;AACT;AAuCO,SAAS,+BACd,KACoB;AACpB,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,EAAE,cAAc,QAAQ,eAAe,KAAK;AACzF,WAAO;AAAA,EACT;AACA,SAAO,EAAE,WAAW,IAAwB;AAC9C;AAEO,SAAS,wCACd,KAC6B;AAC7B,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,EAAE,cAAc,QAAQ,eAAe,KAAK;AACzF,WAAO;AAAA,EACT;AACA,SAAO,EAAE,WAAW,IAAwB;AAC9C;","names":[]}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { ComponentType } from 'react';\n\nimport type {\n DesignPropValue,\n RenderContext,\n ResolveContext,\n ResolveToken,\n} from '@contentful/experiences-sdk-core';\n\n/**\n * Re-exported for ergonomics: customer code authoring `resolveData` doesn't\n * have to dig into the core package for the context type.\n */\nexport type { ResolveContext, ResolveToken };\n\n/**\n * The full Contentful-side payload for a single component instance, exposed\n * via `useContentfulComponent()` to any descendant of a rendered Experience\n * node. Useful for custom design-property resolution outside the SDK's\n * cascade, branching by `componentId` in a generic wrapper, keying\n * analytics on `nodeId`, or rendering a raw-payload panel in preview.\n *\n * Design properties stay in their **raw discriminated form** here (the same\n * shape `ctx.design` carries inside `resolveData`). Token-resolved values are\n * what `useDesignValues()` returns.\n */\nexport interface ContentfulComponent {\n componentId: string;\n nodeId?: string;\n content: Record<string, unknown>;\n design: Record<string, DesignPropValue>;\n resolved?: Record<string, unknown>;\n}\n\n/**\n * Same shape as `ContentfulComponent`, but for an Experience Template node.\n * Exposed via `useContentfulExperienceTemplate()` inside an Experience\n * Template's component tree.\n */\nexport interface ContentfulExperienceTemplate {\n experienceTemplateId: string;\n nodeId?: string;\n content: Record<string, unknown>;\n design: Record<string, DesignPropValue>;\n resolved?: Record<string, unknown>;\n}\n\n/** Render-time experience context, read with `useExperience()`. Declared in core so all adapters share one shape. */\nexport type { RenderContext };\n\n/**\n * Customer-supplied configuration for a single component. The `component`\n * receives the merged props (design + content + resolveData + slots); resolved\n * design values auto-fill matching props and are also readable via\n * `useDesignValues()`. Runtime context and raw payload come from\n * `useExperience()` / `useContentfulComponent()`.\n */\nexport interface ComponentConfig<Props extends object = Record<string, unknown>> {\n /**\n * Lowest-precedence defaults object. Merged in before content / resolveData /\n * slots. Useful for variant fallbacks the editorial layer doesn't always supply.\n */\n defaults?: Partial<Props>;\n /**\n * Optional sync-or-async hook that derives final props from the raw\n * Experience inputs. Runs once during `resolveExperience`, before render.\n */\n resolveData?: (ctx: ResolveContext) => Partial<Props> | Promise<Partial<Props>>;\n /**\n * The React component to render. Receives the merged props.\n */\n component: ComponentType<Props>;\n}\n\n/**\n * Registry value. Register a bare React component for the common case, or\n * the full `ComponentConfig` shape when you need defaults or a `resolveData`\n * hook.\n *\n * button: Button, // bare\n * header: { component: Header, defaults: {...} }, // with defaults\n * card: defineComponent<CardProps>({ component: Card, resolveData: ... }),\n */\nexport type Registration<Props extends object = Record<string, unknown>> =\n ComponentType<Props> | ComponentConfig<Props>;\n\n/**\n * Customer-supplied configuration for a coded Experience Template. Identical in\n * shape and behavior to `ComponentConfig` — an Experience Template is just a\n * node whose implementation lives in the `experienceTemplates` registry. Its\n * slots arrive as named props (a `content` slot becomes a `content` prop), so\n * there is no `children` special case.\n */\nexport interface ExperienceTemplateConfig<Props extends object = Record<string, unknown>> {\n /**\n * Lowest-precedence defaults object. Merged in before content / resolveData /\n * slots. Useful for variant fallbacks the editorial layer doesn't always supply.\n */\n defaults?: Partial<Props>;\n resolveData?: (ctx: ResolveContext) => Partial<Props> | Promise<Partial<Props>>;\n component: ComponentType<Props>;\n}\n\n/**\n * Registry value for Experience Templates. Same dual-shape as component\n * registrations.\n */\nexport type ExperienceTemplateRegistration<Props extends object = Record<string, unknown>> =\n ComponentType<Props> | ExperienceTemplateConfig<Props>;\n\n/**\n * Identity helper — returns the config as-is, but narrows the `resolveData`\n * and `component` parameter types to your declared `Props`.\n */\nexport function defineComponent<Props extends object = Record<string, unknown>>(\n config: ComponentConfig<Props>\n): ComponentConfig<Props> {\n return config;\n}\n\n/**\n * Identity helper — returns the Experience Template config as-is, with `Props`\n * narrowing.\n */\nexport function defineExperienceTemplate<Props extends object = Record<string, unknown>>(\n config: ExperienceTemplateConfig<Props>\n): ExperienceTemplateConfig<Props> {\n return config;\n}\n\n/**\n * Component registry — keyed by `componentId` (last slash-segment of\n * `component.sys.urn`). Per-entry prop narrowing happens at\n * `defineComponent<Props>` or at the bare-component's own type.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- per-entry narrowing happens at the registration author's call site.\nexport type Components = Record<string, Registration<any>>;\n\n/**\n * Experience Template registry — keyed by `experienceTemplateId` (last\n * slash-segment of an Experience Template node's own\n * `experienceTemplate.sys.urn`). Not read from `payload.sys`; templates are\n * ordinary nodes.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- per-entry narrowing happens at the registration author's call site.\nexport type ExperienceTemplates = Record<string, ExperienceTemplateRegistration<any>>;\n\nexport interface Config {\n components: Components;\n experienceTemplates?: ExperienceTemplates;\n /**\n * Resolves `DesignToken` values to runtime values before they reach a\n * component. Unresolved tokens remain raw and produce a diagnostic. See\n * `ResolveToken` in `@contentful/experiences-sdk-core` for the full contract.\n */\n resolveToken?: ResolveToken;\n}\n\n/**\n * Normalize a registry entry — bare component OR config object — into the\n * common `ComponentConfig` shape used by the renderer.\n *\n * React function components are plain functions; `React.memo` /\n * `React.forwardRef` yield objects carrying a `$$typeof` symbol. Both count\n * as bare components; a config object is discriminated by having a\n * `component` field and no `$$typeof`.\n */\nexport function normalizeComponentRegistration<P extends object>(\n reg: Registration<P>\n): ComponentConfig<P> {\n if (typeof reg === 'object' && reg !== null && !('$$typeof' in reg) && 'component' in reg) {\n return reg as ComponentConfig<P>;\n }\n return { component: reg as ComponentType<P> };\n}\n\nexport function normalizeExperienceTemplateRegistration<P extends object>(\n reg: ExperienceTemplateRegistration<P>\n): ExperienceTemplateConfig<P> {\n if (typeof reg === 'object' && reg !== null && !('$$typeof' in reg) && 'component' in reg) {\n return reg as ExperienceTemplateConfig<P>;\n }\n return { component: reg as ComponentType<P> };\n}\n"],"mappings":"AAkHO,SAAS,gBACd,QACwB;AACxB,SAAO;AACT;AAMO,SAAS,yBACd,QACiC;AACjC,SAAO;AACT;AAuCO,SAAS,+BACd,KACoB;AACpB,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,EAAE,cAAc,QAAQ,eAAe,KAAK;AACzF,WAAO;AAAA,EACT;AACA,SAAO,EAAE,WAAW,IAAwB;AAC9C;AAEO,SAAS,wCACd,KAC6B;AAC7B,MAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,EAAE,cAAc,QAAQ,eAAe,KAAK;AACzF,WAAO;AAAA,EACT;AACA,SAAO,EAAE,WAAW,IAAwB;AAC9C;","names":[]}
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Read the current node's resolved design values (viewport-cascaded +
3
- * token-resolved). Returns `{}` when there's no design in scope.
2
+ * Read the current node's token-resolved design values. Returns `{}` when
3
+ * there's no design in scope.
4
4
  *
5
5
  * Pass a type argument to shape the bag, like `useState<T>()`:
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/use-design-values.tsx"],"sourcesContent":["'use client';\n\nimport { useResolvedDesign } from './context';\n\n/**\n * Read the current node's resolved design values (viewport-cascaded +\n * token-resolved). Returns `{}` when there's no design in scope.\n *\n * Pass a type argument to shape the bag, like `useState<T>()`:\n *\n * const design = useDesignValues<{ as?: 'h1' | 'h2' }>();\n *\n * `T` is a convenience assertion, not validated at runtime — treat keys as\n * possibly `undefined`.\n */\nexport function useDesignValues<T extends object = Record<string, unknown>>(): T {\n return (useResolvedDesign() ?? {}) as T;\n}\n"],"mappings":";AAEA,SAAS,yBAAyB;AAa3B,SAAS,kBAAiE;AAC/E,SAAQ,kBAAkB,KAAK,CAAC;AAClC;","names":[]}
1
+ {"version":3,"sources":["../src/use-design-values.tsx"],"sourcesContent":["'use client';\n\nimport { useResolvedDesign } from './context';\n\n/**\n * Read the current node's token-resolved design values. Returns `{}` when\n * there's no design in scope.\n *\n * Pass a type argument to shape the bag, like `useState<T>()`:\n *\n * const design = useDesignValues<{ as?: 'h1' | 'h2' }>();\n *\n * `T` is a convenience assertion, not validated at runtime — treat keys as\n * possibly `undefined`.\n */\nexport function useDesignValues<T extends object = Record<string, unknown>>(): T {\n return (useResolvedDesign() ?? {}) as T;\n}\n"],"mappings":";AAEA,SAAS,yBAAyB;AAa3B,SAAS,kBAAiE;AAC/E,SAAQ,kBAAkB,KAAK,CAAC;AAClC;","names":[]}
@@ -4,7 +4,7 @@ import { resolveExperience } from "@contentful/experiences-sdk-core";
4
4
  function useExperiencePlan(options) {
5
5
  const { payload, initialPlan, resolveOptions } = options;
6
6
  const [plan, setPlan] = useState(initialPlan);
7
- const { config, metadata, debug, initialViewportId, sourceMap } = resolveOptions;
7
+ const { config, metadata, debug, sourceMap } = resolveOptions;
8
8
  useEffect(() => {
9
9
  let isCurrent = true;
10
10
  if (payload === void 0) {
@@ -12,7 +12,7 @@ function useExperiencePlan(options) {
12
12
  isCurrent = false;
13
13
  };
14
14
  }
15
- void resolveExperience(payload, config, { metadata, debug, initialViewportId, sourceMap }).then((nextPlan) => {
15
+ void resolveExperience(payload, config, { metadata, debug, sourceMap }).then((nextPlan) => {
16
16
  if (isCurrent && nextPlan.diagnostics.length === 0) {
17
17
  setPlan(nextPlan);
18
18
  }
@@ -21,7 +21,7 @@ function useExperiencePlan(options) {
21
21
  return () => {
22
22
  isCurrent = false;
23
23
  };
24
- }, [payload, config, metadata, debug, initialViewportId, sourceMap]);
24
+ }, [payload, config, metadata, debug, sourceMap]);
25
25
  return { data: plan };
26
26
  }
27
27
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/use-experience-plan.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { resolveExperience } from '@contentful/experiences-sdk-core';\nimport type {\n ExperiencePayload,\n PortableRenderPlan,\n ResolveExperienceOptions,\n ResolverConfig,\n} from '@contentful/experiences-sdk-core';\n\nexport interface ExperiencePlanResolveOptions extends ResolveExperienceOptions {\n config: ResolverConfig;\n}\n\nexport interface UseExperiencePlanOptions {\n payload: ExperiencePayload | undefined;\n initialPlan?: PortableRenderPlan;\n resolveOptions: ExperiencePlanResolveOptions;\n}\n\nexport interface UseExperiencePlanResult {\n readonly data: PortableRenderPlan | undefined;\n}\n\nexport function useExperiencePlan(options: UseExperiencePlanOptions): UseExperiencePlanResult {\n const { payload, initialPlan, resolveOptions } = options;\n const [plan, setPlan] = useState<PortableRenderPlan | undefined>(initialPlan);\n const { config, metadata, debug, initialViewportId, sourceMap } = resolveOptions;\n\n useEffect(() => {\n let isCurrent = true;\n\n if (payload === undefined) {\n return () => {\n isCurrent = false;\n };\n }\n\n void resolveExperience(payload, config, { metadata, debug, initialViewportId, sourceMap })\n .then((nextPlan) => {\n if (isCurrent && nextPlan.diagnostics.length === 0) {\n setPlan(nextPlan);\n }\n })\n .catch(() => {\n // Keep the last usable experience when resolving an update fails.\n });\n\n return () => {\n isCurrent = false;\n };\n }, [payload, config, metadata, debug, initialViewportId, sourceMap]);\n\n return { data: plan };\n}\n"],"mappings":";AAEA,SAAS,WAAW,gBAAgB;AAEpC,SAAS,yBAAyB;AAsB3B,SAAS,kBAAkB,SAA4D;AAC5F,QAAM,EAAE,SAAS,aAAa,eAAe,IAAI;AACjD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAyC,WAAW;AAC5E,QAAM,EAAE,QAAQ,UAAU,OAAO,mBAAmB,UAAU,IAAI;AAElE,YAAU,MAAM;AACd,QAAI,YAAY;AAEhB,QAAI,YAAY,QAAW;AACzB,aAAO,MAAM;AACX,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,SAAK,kBAAkB,SAAS,QAAQ,EAAE,UAAU,OAAO,mBAAmB,UAAU,CAAC,EACtF,KAAK,CAAC,aAAa;AAClB,UAAI,aAAa,SAAS,YAAY,WAAW,GAAG;AAClD,gBAAQ,QAAQ;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,MAAM;AAAA,IAEb,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,SAAS,QAAQ,UAAU,OAAO,mBAAmB,SAAS,CAAC;AAEnE,SAAO,EAAE,MAAM,KAAK;AACtB;","names":[]}
1
+ {"version":3,"sources":["../src/use-experience-plan.ts"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport { resolveExperience } from '@contentful/experiences-sdk-core';\nimport type {\n ExperiencePayload,\n PortableRenderPlan,\n ResolveExperienceOptions,\n ResolverConfig,\n} from '@contentful/experiences-sdk-core';\n\nexport interface ExperiencePlanResolveOptions extends ResolveExperienceOptions {\n config: ResolverConfig;\n}\n\nexport interface UseExperiencePlanOptions {\n payload: ExperiencePayload | undefined;\n initialPlan?: PortableRenderPlan;\n resolveOptions: ExperiencePlanResolveOptions;\n}\n\nexport interface UseExperiencePlanResult {\n readonly data: PortableRenderPlan | undefined;\n}\n\nexport function useExperiencePlan(options: UseExperiencePlanOptions): UseExperiencePlanResult {\n const { payload, initialPlan, resolveOptions } = options;\n const [plan, setPlan] = useState<PortableRenderPlan | undefined>(initialPlan);\n const { config, metadata, debug, sourceMap } = resolveOptions;\n\n useEffect(() => {\n let isCurrent = true;\n\n if (payload === undefined) {\n return () => {\n isCurrent = false;\n };\n }\n\n void resolveExperience(payload, config, { metadata, debug, sourceMap })\n .then((nextPlan) => {\n if (isCurrent && nextPlan.diagnostics.length === 0) {\n setPlan(nextPlan);\n }\n })\n .catch(() => {\n // Keep the last usable experience when resolving an update fails.\n });\n\n return () => {\n isCurrent = false;\n };\n }, [payload, config, metadata, debug, sourceMap]);\n\n return { data: plan };\n}\n"],"mappings":";AAEA,SAAS,WAAW,gBAAgB;AAEpC,SAAS,yBAAyB;AAsB3B,SAAS,kBAAkB,SAA4D;AAC5F,QAAM,EAAE,SAAS,aAAa,eAAe,IAAI;AACjD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAyC,WAAW;AAC5E,QAAM,EAAE,QAAQ,UAAU,OAAO,UAAU,IAAI;AAE/C,YAAU,MAAM;AACd,QAAI,YAAY;AAEhB,QAAI,YAAY,QAAW;AACzB,aAAO,MAAM;AACX,oBAAY;AAAA,MACd;AAAA,IACF;AAEA,SAAK,kBAAkB,SAAS,QAAQ,EAAE,UAAU,OAAO,UAAU,CAAC,EACnE,KAAK,CAAC,aAAa;AAClB,UAAI,aAAa,SAAS,YAAY,WAAW,GAAG;AAClD,gBAAQ,QAAQ;AAAA,MAClB;AAAA,IACF,CAAC,EACA,MAAM,MAAM;AAAA,IAEb,CAAC;AAEH,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,SAAS,QAAQ,UAAU,OAAO,SAAS,CAAC;AAEhD,SAAO,EAAE,MAAM,KAAK;AACtB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experiences-react",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "React adapter for Contentful Experiences — Server and Client renderers over a PortableRenderPlan",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,10 +30,10 @@
30
30
  "directory": "packages/adapter-react"
31
31
  },
32
32
  "dependencies": {
33
- "@contentful/experiences-client": "0.8.0",
34
- "@contentful/experiences-sdk-core": "0.11.0",
35
- "@contentful/experiences-design": "0.11.0",
36
- "@contentful/experiences-live-preview": "0.5.0"
33
+ "@contentful/experiences-client": "0.9.0",
34
+ "@contentful/experiences-sdk-core": "0.12.0",
35
+ "@contentful/experiences-design": "0.12.0",
36
+ "@contentful/experiences-live-preview": "0.6.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "jsdom": "^30.0.1"
@@ -1,18 +0,0 @@
1
- import { ViewportDef } from '@contentful/experiences-sdk-core';
2
-
3
- /**
4
- * @deprecated Viewports are being removed from the Experiences APIs, with no
5
- * direct replacement — use CSS media queries or `window.matchMedia` instead.
6
- * Will not be removed before 2026-10-06.
7
- */
8
- interface UseActiveViewportResult {
9
- activeViewportIndex: number;
10
- }
11
- /**
12
- * @deprecated Viewports are being removed from the Experiences APIs, with no
13
- * direct replacement — use CSS media queries or `window.matchMedia` instead.
14
- * Will not be removed before 2026-10-06.
15
- */
16
- declare function useActiveViewport(viewports: ViewportDef[], initialViewportId?: string): UseActiveViewportResult;
17
-
18
- export { type UseActiveViewportResult, useActiveViewport };
@@ -1,43 +0,0 @@
1
- "use client";
2
- import { useEffect, useState } from "react";
3
- import { createMediaQueryMatchers, getViewportIndex } from "@contentful/experiences-design";
4
- function useMediaQueryMatchers(viewports) {
5
- const [, initialMatches] = createMediaQueryMatchers(viewports);
6
- const [matches, setMatches] = useState(initialMatches);
7
- useEffect(() => {
8
- const [matchers] = createMediaQueryMatchers(viewports);
9
- const listeners = matchers.map(({ index, signal }) => {
10
- const onChange = () => setMatches((prev) => {
11
- const next = [...prev];
12
- next[index] = signal.matches;
13
- return next;
14
- });
15
- signal.addEventListener("change", onChange);
16
- return onChange;
17
- });
18
- return () => {
19
- listeners.forEach((listener, i) => {
20
- const matcher = matchers[i];
21
- if (matcher) matcher.signal.removeEventListener("change", listener);
22
- });
23
- };
24
- }, [viewports]);
25
- return matches;
26
- }
27
- function useActiveViewport(viewports, initialViewportId) {
28
- const matches = useMediaQueryMatchers(viewports);
29
- const [activeViewportIndex, setActiveViewportIndex] = useState(
30
- () => getViewportIndex(viewports, initialViewportId)
31
- );
32
- useEffect(() => {
33
- const currentIndex = matches.findLastIndex((isMatch) => isMatch);
34
- if (currentIndex !== -1 && currentIndex !== activeViewportIndex) {
35
- setActiveViewportIndex(currentIndex);
36
- }
37
- }, [activeViewportIndex, matches]);
38
- return { activeViewportIndex };
39
- }
40
- export {
41
- useActiveViewport
42
- };
43
- //# sourceMappingURL=use-active-viewport.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/use-active-viewport.ts"],"sourcesContent":["/*\n * Reactive viewport-matcher hook.\n *\n * \"Active viewport\" = the last-matching media query in the ordered viewports\n * list. With desktop-first ordering (`*`, `<992px`, `<576px`), the narrowest\n * matching query wins. With mobile-first ordering, the widest wins. Either\n * way: the last `findLastIndex` of `mediaQueryMatches[i] === true`.\n *\n * Pass `initialViewportId` (typically derived from User-Agent on the server)\n * to seed the first render so SSR + client agree on viewport before\n * window.matchMedia takes over.\n */\n\n'use client';\n\nimport { useEffect, useState } from 'react';\n\nimport type { ViewportDef } from '@contentful/experiences-sdk-core';\nimport { createMediaQueryMatchers, getViewportIndex } from '@contentful/experiences-design';\n\nfunction useMediaQueryMatchers(viewports: ViewportDef[]): boolean[] {\n const [, initialMatches] = createMediaQueryMatchers(viewports);\n const [matches, setMatches] = useState<boolean[]>(initialMatches);\n\n useEffect(() => {\n const [matchers] = createMediaQueryMatchers(viewports);\n const listeners = matchers.map(({ index, signal }) => {\n const onChange = (): void =>\n setMatches((prev) => {\n const next = [...prev];\n next[index] = signal.matches;\n return next;\n });\n signal.addEventListener('change', onChange);\n return onChange;\n });\n return () => {\n listeners.forEach((listener, i) => {\n const matcher = matchers[i];\n if (matcher) matcher.signal.removeEventListener('change', listener);\n });\n };\n }, [viewports]);\n\n return matches;\n}\n\n/**\n * @deprecated Viewports are being removed from the Experiences APIs, with no\n * direct replacement — use CSS media queries or `window.matchMedia` instead.\n * Will not be removed before 2026-10-06.\n */\nexport interface UseActiveViewportResult {\n activeViewportIndex: number;\n}\n\n/**\n * @deprecated Viewports are being removed from the Experiences APIs, with no\n * direct replacement — use CSS media queries or `window.matchMedia` instead.\n * Will not be removed before 2026-10-06.\n */\nexport function useActiveViewport(\n viewports: ViewportDef[],\n initialViewportId?: string\n): UseActiveViewportResult {\n const matches = useMediaQueryMatchers(viewports);\n const [activeViewportIndex, setActiveViewportIndex] = useState<number>(() =>\n getViewportIndex(viewports, initialViewportId)\n );\n\n useEffect(() => {\n const currentIndex = matches.findLastIndex((isMatch) => isMatch);\n if (currentIndex !== -1 && currentIndex !== activeViewportIndex) {\n setActiveViewportIndex(currentIndex);\n }\n }, [activeViewportIndex, matches]);\n\n return { activeViewportIndex };\n}\n"],"mappings":";AAeA,SAAS,WAAW,gBAAgB;AAGpC,SAAS,0BAA0B,wBAAwB;AAE3D,SAAS,sBAAsB,WAAqC;AAClE,QAAM,CAAC,EAAE,cAAc,IAAI,yBAAyB,SAAS;AAC7D,QAAM,CAAC,SAAS,UAAU,IAAI,SAAoB,cAAc;AAEhE,YAAU,MAAM;AACd,UAAM,CAAC,QAAQ,IAAI,yBAAyB,SAAS;AACrD,UAAM,YAAY,SAAS,IAAI,CAAC,EAAE,OAAO,OAAO,MAAM;AACpD,YAAM,WAAW,MACf,WAAW,CAAC,SAAS;AACnB,cAAM,OAAO,CAAC,GAAG,IAAI;AACrB,aAAK,KAAK,IAAI,OAAO;AACrB,eAAO;AAAA,MACT,CAAC;AACH,aAAO,iBAAiB,UAAU,QAAQ;AAC1C,aAAO;AAAA,IACT,CAAC;AACD,WAAO,MAAM;AACX,gBAAU,QAAQ,CAAC,UAAU,MAAM;AACjC,cAAM,UAAU,SAAS,CAAC;AAC1B,YAAI,QAAS,SAAQ,OAAO,oBAAoB,UAAU,QAAQ;AAAA,MACpE,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,SAAS,CAAC;AAEd,SAAO;AACT;AAgBO,SAAS,kBACd,WACA,mBACyB;AACzB,QAAM,UAAU,sBAAsB,SAAS;AAC/C,QAAM,CAAC,qBAAqB,sBAAsB,IAAI;AAAA,IAAiB,MACrE,iBAAiB,WAAW,iBAAiB;AAAA,EAC/C;AAEA,YAAU,MAAM;AACd,UAAM,eAAe,QAAQ,cAAc,CAAC,YAAY,OAAO;AAC/D,QAAI,iBAAiB,MAAM,iBAAiB,qBAAqB;AAC/D,6BAAuB,YAAY;AAAA,IACrC;AAAA,EACF,GAAG,CAAC,qBAAqB,OAAO,CAAC;AAEjC,SAAO,EAAE,oBAAoB;AAC/B;","names":[]}