@contentful/experiences-core 0.0.1 → 0.3.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +16 -0
  3. package/dist/index.d.ts +2 -24
  4. package/dist/index.js +5 -2402
  5. package/dist/index.js.map +1 -1
  6. package/dist/resolve-experience.d.ts +35 -0
  7. package/dist/resolve-experience.js +119 -0
  8. package/dist/resolve-experience.js.map +1 -0
  9. package/dist/types.d.ts +196 -235
  10. package/dist/types.js +1 -0
  11. package/dist/types.js.map +1 -0
  12. package/package.json +23 -66
  13. package/LICENSE +0 -21
  14. package/dist/communication/sendMessage.d.ts +0 -6
  15. package/dist/constants.d.ts +0 -106
  16. package/dist/constants.js +0 -144
  17. package/dist/constants.js.map +0 -1
  18. package/dist/deep-binding/DeepReference.d.ts +0 -28
  19. package/dist/definitions/components.d.ts +0 -8
  20. package/dist/definitions/styles.d.ts +0 -11
  21. package/dist/entity/EditorEntityStore.d.ts +0 -34
  22. package/dist/entity/EditorModeEntityStore.d.ts +0 -29
  23. package/dist/entity/EntityStore.d.ts +0 -68
  24. package/dist/entity/EntityStoreBase.d.ts +0 -49
  25. package/dist/enums.d.ts +0 -6
  26. package/dist/exports.d.ts +0 -3
  27. package/dist/exports.js +0 -2
  28. package/dist/exports.js.map +0 -1
  29. package/dist/fetchers/createExperience.d.ts +0 -20
  30. package/dist/fetchers/fetchById.d.ts +0 -20
  31. package/dist/fetchers/fetchBySlug.d.ts +0 -20
  32. package/dist/registries/designTokenRegistry.d.ts +0 -12
  33. package/dist/utils/breakpoints.d.ts +0 -12
  34. package/dist/utils/components.d.ts +0 -4
  35. package/dist/utils/domValues.d.ts +0 -15
  36. package/dist/utils/isLink.d.ts +0 -5
  37. package/dist/utils/isLinkToAsset.d.ts +0 -5
  38. package/dist/utils/pathSchema.d.ts +0 -30
  39. package/dist/utils/styleUtils/stylesUtils.d.ts +0 -20
  40. package/dist/utils/supportedModes.d.ts +0 -5
  41. package/dist/utils/transformers/transformBoundContentValue.d.ts +0 -8
  42. package/dist/utils/typeguards.d.ts +0 -6
  43. package/dist/utils/utils.d.ts +0 -46
  44. package/dist/utils/validations.d.ts +0 -15
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resolve-experience.ts"],"sourcesContent":["/*\n * Single async entry that turns an XDA Experience payload into a\n * runtime-neutral PortableRenderPlan ready to render.\n *\n * v1 behavior:\n * - Walk the payload's nodes recursively. Each ComponentType node becomes\n * a PortableRenderNode with `registration.componentTypeId` extracted\n * from `componentType.sys.urn` (last slash-segment).\n * - Split content + design properties onto `node.props.{content,design}`.\n * Design-prop envelopes (DesignToken / ManualDesignValue / ValuesByViewport)\n * are preserved on the IR; the design package unwraps them at render time.\n * - Template-variant nodes are skipped with a console.warn — out of v1 scope.\n * - For every component whose registration declares `resolveData`, run the\n * resolver (sync or async) in parallel with peers, and attach the result\n * to `node.props.resolved`.\n * - Unknown component-type-id is a render-time concern (handled by the\n * framework adapter via `renderUnknown`); the IR still emits the node.\n */\n\nimport type {\n ComponentTypeNode,\n DesignPropValue,\n ExperienceContext,\n ExperienceNode,\n ExperiencePayload,\n PortableRenderNode,\n PortableRenderPlan,\n PortableTemplate,\n ResolveContext,\n} from './types';\n\n/**\n * Structural type the resolver walker depends on. Matches the React\n * adapter's `Config` shape but doesn't require importing it — render-core\n * stays decoupled from React.\n */\nexport interface ResolverConfig {\n components: Record<\n string,\n {\n resolveData?: (\n ctx: ResolveContext\n ) => Record<string, unknown> | Promise<Record<string, unknown>>;\n }\n >;\n templates?: Record<\n string,\n {\n resolveData?: (\n ctx: ResolveContext\n ) => Record<string, unknown> | Promise<Record<string, unknown>>;\n }\n >;\n}\n\nexport interface ResolveExperienceOptions {\n /**\n * Per-render runtime context exposed to every resolver as `ctx.experience`.\n * Defaults to `{ isPreview: false, metadata: {} }`.\n */\n experience?: Partial<ExperienceContext>;\n}\n\nconst DEFAULT_EXPERIENCE: ExperienceContext = {\n isPreview: false,\n metadata: {},\n viewports: [],\n};\n\nfunction isComponentTypeNode(node: ExperienceNode): node is ComponentTypeNode {\n return 'componentType' in node;\n}\n\n/**\n * Extract the flat id (componentType or template) from its `ResourceLink`\n * URN. Real URN shapes:\n * crn:contentful:::experience:spaces/$self/environments/$self/componentTypes/<id>\n * crn:contentful:::experience:spaces/$self/environments/$self/templates/<id>\n *\n * The id is the final path segment. We split on `/` and take the last\n * non-empty piece so this also tolerates trailing slashes or alternative\n * prefix shapes.\n */\nfunction extractIdFromUrn(urn: string): string {\n const segments = urn.split('/').filter((s) => s.length > 0);\n return segments[segments.length - 1] ?? urn;\n}\n\n/**\n * Recursively turn a payload node into an IR node. The collected `nodeRefs`\n * array is for the resolver pass — every built node with a registered\n * resolver gets a reference appended so we can run them in parallel without\n * walking the tree twice.\n */\nfunction buildNode(\n node: ExperienceNode,\n config: ResolverConfig,\n nodeRefs: PortableRenderNode[]\n): PortableRenderNode | null {\n if (!isComponentTypeNode(node)) {\n if (typeof console !== 'undefined') {\n console.warn(\n '[@contentful/experiences-core] Skipping Template-variant node — Templates are not supported in v1.'\n );\n }\n return null;\n }\n\n const componentTypeId = extractIdFromUrn(node.componentType.sys.urn);\n\n const slots: Record<string, PortableRenderNode[]> = {};\n if (node.slots) {\n for (const [slotName, children] of Object.entries(node.slots)) {\n if (!Array.isArray(children)) {\n throw new TypeError(\n `Slot \"${slotName}\" on component \"${componentTypeId}\" must be an array of nodes.`\n );\n }\n const built: PortableRenderNode[] = [];\n for (const child of children) {\n const childNode = buildNode(child, config, nodeRefs);\n if (childNode === null) continue;\n built.push(childNode);\n }\n slots[slotName] = built;\n }\n }\n\n const built: PortableRenderNode = {\n registration: { componentTypeId },\n props: {\n content: { ...(node.contentProperties ?? {}) },\n design: { ...(node.designProperties ?? {}) } as Record<string, DesignPropValue>,\n },\n slots,\n };\n if (node.id) built.nodeId = node.id;\n if (config.components[componentTypeId]?.resolveData) {\n nodeRefs.push(built);\n }\n return built;\n}\n\n/**\n * Turns an Experience payload (XDA response shape) into a PortableRenderPlan\n * ready to hand to a renderer. Walks the tree, classifies props, captures\n * slots, and runs any component-declared `resolveData` hooks (sync or async)\n * in parallel.\n *\n * Implementation note: the function is always async — even when no component\n * declares a resolver, the cost is one microtask. Customers get a single\n * uniform call site.\n */\nexport async function resolveExperience(\n payload: ExperiencePayload,\n config: ResolverConfig,\n options: ResolveExperienceOptions = {}\n): Promise<PortableRenderPlan> {\n // Pass 1: walk the payload into the IR. Collect refs to nodes that need\n // resolveData so pass 2 can run them in parallel without re-walking.\n const nodeRefs: PortableRenderNode[] = [];\n const nodes: PortableRenderNode[] = [];\n for (const node of payload.nodes) {\n const built = buildNode(node, config, nodeRefs);\n if (built !== null) nodes.push(built);\n }\n\n // Build the page-level template stub if the payload carries one. XDA\n // payloads don't yet emit template-level content/design properties, so\n // the IR carries empty bags.\n const templateUrn = payload.sys?.template?.sys.urn;\n let template: PortableTemplate | undefined;\n if (typeof templateUrn === 'string' && templateUrn.length > 0) {\n template = {\n templateId: extractIdFromUrn(templateUrn),\n props: { content: {}, design: {} },\n };\n }\n\n // Pass 2: run resolveData hooks for components AND the template in parallel.\n // `viewports` is always sourced from the payload — caller-supplied\n // options.experience.viewports is ignored (the list is fact, not opinion).\n const experience: ExperienceContext = {\n ...DEFAULT_EXPERIENCE,\n ...options.experience,\n metadata: {\n ...DEFAULT_EXPERIENCE.metadata,\n ...(options.experience?.metadata ?? {}),\n },\n viewports: payload.viewports,\n };\n\n const tasks: Array<Promise<void>> = [];\n\n for (const node of nodeRefs) {\n const resolver = config.components[node.registration.componentTypeId]?.resolveData;\n if (!resolver) continue;\n const ctx: ResolveContext = {\n content: node.props.content,\n design: node.props.design,\n experience,\n };\n tasks.push(\n Promise.resolve(resolver(ctx)).then((resolved) => {\n node.props.resolved = resolved;\n })\n );\n }\n\n if (template) {\n const tplResolver = config.templates?.[template.templateId]?.resolveData;\n if (tplResolver) {\n const ctx: ResolveContext = {\n content: template.props.content,\n design: template.props.design,\n experience,\n };\n const tpl = template;\n tasks.push(\n Promise.resolve(tplResolver(ctx)).then((resolved) => {\n tpl.props.resolved = resolved;\n })\n );\n }\n }\n\n if (tasks.length > 0) await Promise.all(tasks);\n\n return {\n viewports: payload.viewports,\n nodes,\n ...(template ? { template } : {}),\n };\n}\n"],"mappings":"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","names":["built"]}
package/dist/types.d.ts CHANGED
@@ -1,244 +1,205 @@
1
- import { Entry, Asset, AssetFile } from 'contentful';
2
- import { SCROLL_STATES, OUTGOING_EVENTS, INCOMING_EVENTS, INTERNAL_EVENTS } from './constants.js';
3
- import { EntityStore } from './entity/EntityStore.js';
4
- import { Document } from '@contentful/rich-text-types';
5
- import { ComponentDefinitionPropertyType, ComponentPropertyValue, ExperienceDataSource, ExperienceUnboundValues, Breakpoint, ExperienceComponentTree, ExperienceUsedComponents, ExperienceComponentSettings, ValuesByBreakpoint, PrimitiveValue } from '@contentful/experiences-validators';
6
- export { BoundValue, Breakpoint, ComponentDefinitionPropertyType as ComponentDefinitionVariableType, ComponentPropertyValue, ComponentTreeNode, ComponentValue, DesignValue, ExperienceComponentSettings, ExperienceDataSource, ExperienceUnboundValues, PrimitiveValue, SchemaVersions, UnboundValue, ValuesByBreakpoint } from '@contentful/experiences-validators';
7
-
8
- type ScrollStateKey = keyof typeof SCROLL_STATES;
9
- type ScrollState = (typeof SCROLL_STATES)[ScrollStateKey];
10
- type OutgoingEventKey = keyof typeof OUTGOING_EVENTS;
11
- type OutgoingEvent = (typeof OUTGOING_EVENTS)[OutgoingEventKey];
12
- type IncomingEventKey = keyof typeof INCOMING_EVENTS;
13
- type IncomingEvent = (typeof INCOMING_EVENTS)[IncomingEventKey];
14
- type InternalEventKey = keyof typeof INTERNAL_EVENTS;
15
- type InternalEvent = (typeof INTERNAL_EVENTS)[InternalEventKey];
16
- interface Link<T extends string> {
17
- sys: {
18
- type: 'Link';
19
- linkType: T;
20
- id: string;
21
- };
1
+ /**
2
+ * Per-render runtime context attached to every customer component as the
3
+ * `experience` prop, and passed to every `resolveData` hook as `ctx.experience`.
4
+ * The conventional, single injection point — kept small at v1.
5
+ *
6
+ * `viewports` (the *list*) is here so customer resolvers can inspect what
7
+ * viewports an Experience declares (e.g. "is there a mobile viewport?").
8
+ * The *active* viewport is render-time only and lives on the framework
9
+ * adapter's RenderContext — exposing it here would mean async resolvers
10
+ * re-fire on every viewport change, which would be a footgun.
11
+ */
12
+ interface ExperienceContext {
13
+ isPreview: boolean;
14
+ metadata: Record<string, unknown>;
15
+ viewports: ViewportDef[];
22
16
  }
23
- type VariableFormats = 'URL';
24
- type ValidationOption<T extends ComponentDefinitionPropertyType> = {
25
- value: T extends 'Text' ? string : T extends 'Number' ? number : never;
26
- displayName?: string;
27
- };
28
- type ComponentDefinitionVariableValidation<T extends ComponentDefinitionPropertyType> = {
29
- required?: boolean;
30
- in?: ValidationOption<T>[];
31
- format?: VariableFormats;
32
- };
33
- interface ComponentDefinitionVariableBase<T extends ComponentDefinitionPropertyType> {
34
- type: T;
35
- validations?: ComponentDefinitionVariableValidation<T>;
36
- group?: 'style' | 'content';
37
- description?: string;
38
- displayName?: string;
39
- defaultValue?: string | boolean | number | Record<any, any>;
40
- }
41
- type ComponentDefinitionVariable<T extends ComponentDefinitionPropertyType = ComponentDefinitionPropertyType> = ComponentDefinitionVariableBase<T>;
42
- type ComponentDefinition<T extends ComponentDefinitionPropertyType = ComponentDefinitionPropertyType> = {
17
+ /**
18
+ * One viewport definition from a delivered Experience. The `query` is the
19
+ * Contentful media-query DSL ("*" | "<992px" | ">1200px"), not raw CSS.
20
+ *
21
+ * The first viewport in the list is conventionally the wildcard ("*") that
22
+ * always matches. The viewport order encodes the cascade direction —
23
+ * desktop-first (descending) or mobile-first (ascending).
24
+ */
25
+ interface ViewportDef {
43
26
  id: string;
44
- name: string;
45
- category?: string;
46
- thumbnailUrl?: string;
47
- variables: Partial<Record<ContainerStyleVariableName, ComponentDefinitionVariable<T>>> & Record<string, ComponentDefinitionVariable<T>>;
48
- builtInStyles?: Array<keyof Omit<StyleProps, 'cfHyperlink' | 'cfOpenInNewTab'>>;
49
- children?: boolean;
50
- tooltip?: {
51
- imageUrl?: string;
52
- description: string;
27
+ query: string;
28
+ displayName: string;
29
+ previewSize: string;
30
+ }
31
+ /**
32
+ * Discriminated design-property value as it arrives from XDA. v1 accepts:
33
+ * - ManualDesignValue: an explicit scalar (no viewport involved).
34
+ * - ValuesByViewport: a viewport-keyed bag where each entry is itself a
35
+ * ManualDesignValue or DesignToken.
36
+ * - DesignToken: a token reference, passed through to customer components
37
+ * as-is for v1. Resolution lands in the future tokens package.
38
+ */
39
+ type DesignPropValue = ManualDesignValue | DesignToken | ValuesByViewport;
40
+ interface ManualDesignValue {
41
+ type: 'ManualDesignValue';
42
+ value: string | number | boolean;
43
+ }
44
+ interface DesignToken {
45
+ type: 'DesignToken';
46
+ value: string;
47
+ }
48
+ interface ValuesByViewport {
49
+ type: 'ValuesByViewport';
50
+ values: Record<string, ManualDesignValue | DesignToken>;
51
+ }
52
+ /**
53
+ * Resource-link reference to a registered Component Type. The `urn` carries
54
+ * the type id; the build-plan extracts the id by taking the segment after
55
+ * the last slash.
56
+ */
57
+ interface ComponentTypeRef {
58
+ sys: {
59
+ type: 'ResourceLink';
60
+ linkType: 'Contentful:ComponentType';
61
+ urn: string;
53
62
  };
54
- };
55
- type ComponentRegistration = {
56
- component: React.ElementType;
57
- definition: ComponentDefinition;
58
- options?: {
59
- wrapComponent?: boolean;
60
- wrapContainerTag?: keyof JSX.IntrinsicElements;
63
+ }
64
+ /**
65
+ * Resource-link reference to a Template. Templates are out of v1 scope and
66
+ * are skipped at plan-build time with a diagnostic.
67
+ */
68
+ interface TemplateRef {
69
+ sys: {
70
+ type: 'ResourceLink';
71
+ linkType: 'Contentful:Template';
72
+ urn: string;
61
73
  };
62
- };
63
- type ComponentRegistrationOptions = {
64
- enabledBuiltInComponents?: string[];
65
- };
66
- type Binding = {
67
- spaceId: string;
68
- environmentId: string;
69
- entityId: string;
70
- entityType: 'Entry' | 'Asset' | 'ContentType';
71
- path: string[];
72
- };
73
- type ComponentBinding = Record<string, Binding>;
74
- type BindingMap = Record<string, ComponentBinding>;
75
- type BindingMapByBlockId = Record<string, BindingMap>;
76
- type DataSourceEntryValueType = Link<'Entry' | 'Asset'>;
77
- /** Type of a single node of the experience tree exchanged via postMessage between the SDK and Contentful Web app */
78
- type ExperienceTreeNode = {
79
- type: 'block' | 'root' | 'editorRoot' | 'designComponent' | 'designComponentBlock' | 'assembly' | 'assemblyBlock';
80
- data: {
81
- id: string;
82
- blockId?: string;
83
- assembly?: {
84
- id: string;
85
- componentId: string;
86
- nodeLocation: string | null;
87
- };
88
- props: Record<string, ComponentPropertyValue>;
89
- dataSource: ExperienceDataSource;
90
- unboundValues: ExperienceUnboundValues;
91
- breakpoints: Breakpoint[];
74
+ }
75
+ /**
76
+ * One node from `GetExperienceViewResponse.nodes` (or any `slots[name]`).
77
+ * Discriminated by which of `componentType` / `template` is present.
78
+ */
79
+ type ExperienceNode = ComponentTypeNode | TemplateNode;
80
+ interface ComponentTypeNode {
81
+ componentType: ComponentTypeRef;
82
+ id?: string;
83
+ contentProperties?: Record<string, unknown>;
84
+ designProperties?: Record<string, DesignPropValue>;
85
+ slots?: Record<string, ExperienceNode[]>;
86
+ contentBindings?: string;
87
+ }
88
+ interface TemplateNode {
89
+ template: TemplateRef;
90
+ id?: string;
91
+ contentProperties?: Record<string, unknown>;
92
+ designProperties?: Record<string, DesignPropValue>;
93
+ slots?: Record<string, ExperienceNode[]>;
94
+ contentBindings?: string;
95
+ }
96
+ /**
97
+ * Top-level `sys` block on an Experience payload. The bits the SDK actually
98
+ * reads are typed; everything else is left loose because the upstream
99
+ * type carries dozens of editor/audit fields the renderer doesn't care about.
100
+ */
101
+ interface ExperienceSys {
102
+ /**
103
+ * Optional page-level template reference. When present, the renderer wraps
104
+ * the experience nodes with the matching template registered in the
105
+ * customer's Config. When absent, nodes render at the top level.
106
+ */
107
+ template?: TemplateRef;
108
+ [key: string]: unknown;
109
+ }
110
+ /**
111
+ * Top-level Experience payload as returned by the Experience Delivery API
112
+ * (`GetExperienceViewResponse` from `@contentful/experience-delivery`).
113
+ *
114
+ * Structurally compatible with the upstream type — no normalization step
115
+ * required when consuming a delivery-client response.
116
+ */
117
+ interface ExperiencePayload {
118
+ viewports: ViewportDef[];
119
+ nodes: ExperienceNode[];
120
+ errors?: unknown[];
121
+ extensions?: unknown;
122
+ sys?: ExperienceSys;
123
+ }
124
+ /**
125
+ * Per-node context handed to a component's `resolveData` resolver. Carries
126
+ * the raw content + design props from the payload (design envelopes are NOT
127
+ * pre-resolved against a viewport — viewport resolution stays a render-time
128
+ * concern so client viewport changes don't re-trigger async resolvers).
129
+ */
130
+ interface ResolveContext {
131
+ content: Record<string, unknown>;
132
+ design: Record<string, DesignPropValue>;
133
+ experience: ExperienceContext;
134
+ }
135
+ /**
136
+ * Registration metadata for a single instance — the SDK's interpreted
137
+ * pointer to the customer's component implementation. Today carries only
138
+ * the resolved component-type id; capabilities (state requirements,
139
+ * supported events, lifecycle hints, fallback ids) land here when needed.
140
+ */
141
+ interface PortableRegistration {
142
+ componentTypeId: string;
143
+ }
144
+ /**
145
+ * The IR — one node per component instance. The seam that lets non-React
146
+ * adapters (Angular, SwiftUI, Compose) consume the same interpretation.
147
+ *
148
+ * Design props preserve the discriminated envelope as they arrived. Adapters
149
+ * unwrap to plain scalars at render time, given an active viewport.
150
+ * (DesignToken envelopes pass through unwrapped — customer components decide
151
+ * how to resolve them in v1.)
152
+ *
153
+ * `props.resolved` is populated by `resolveExperience` from any
154
+ * customer-supplied `resolveData` resolver and merged into the final prop bag
155
+ * after content + design but before slot props.
156
+ */
157
+ interface PortableRenderNode {
158
+ /**
159
+ * Optional. Passed through from the XDA payload's `id` field when the
160
+ * editor supplies one. The SDK does NOT auto-generate ids; adapters fall
161
+ * back to the array index for React keys / debug labels when absent.
162
+ */
163
+ nodeId?: string;
164
+ registration: PortableRegistration;
165
+ props: {
166
+ content: Record<string, unknown>;
167
+ design: Record<string, DesignPropValue>;
168
+ resolved?: Record<string, unknown>;
92
169
  };
93
- children: ExperienceTreeNode[];
94
- parentId?: string;
95
- };
96
- /** Type of the tree data structure exchanged via postMessage between the SDK and Contentful Web app */
97
- type ExperienceTree = {
98
- root: ExperienceTreeNode;
99
- };
100
- type ExternalSDKMode = 'preview' | 'delivery';
101
- type InternalSDKMode = ExternalSDKMode | 'editor';
170
+ slots: Record<string, PortableRenderNode[]>;
171
+ }
102
172
  /**
103
- * Internally defined style variables are prefix with `cf` to avoid
104
- * collisions with user defined variables.
173
+ * Interpreted page-level template the optional wrapper around the
174
+ * experience tree. `templateId` is extracted from
175
+ * `payload.sys.template.sys.urn` (last slash-segment).
176
+ *
177
+ * Templates carry the same prop-resolution shape as components: content +
178
+ * design envelopes plus an optional `resolved` bag from a `resolveData` hook.
179
+ * v1 payloads from XDA don't carry template-level content/design properties
180
+ * yet, but the IR makes room for them so the API doesn't need to break later.
105
181
  */
106
- type StyleProps = {
107
- cfHorizontalAlignment: 'start' | 'end' | 'center';
108
- cfVerticalAlignment: 'start' | 'end' | 'center';
109
- cfMargin: string;
110
- cfPadding: string;
111
- cfBackgroundColor: string;
112
- cfWidth: string;
113
- cfMaxWidth: string;
114
- cfHeight: string;
115
- cfFlexDirection: 'row' | 'column';
116
- cfFlexWrap: 'nowrap' | 'wrap';
117
- cfBorder: string;
118
- cfBorderRadius: string;
119
- cfGap: string;
120
- cfHyperlink: string;
121
- cfImageAsset: OptimizedImageAsset | string;
122
- cfImageOptions: ImageOptions;
123
- cfBackgroundImageUrl: OptimizedBackgroundImageAsset | string;
124
- cfBackgroundImageOptions: BackgroundImageOptions;
125
- cfOpenInNewTab: boolean;
126
- cfFontSize: string;
127
- cfFontWeight: string;
128
- cfLineHeight: string;
129
- cfLetterSpacing: string;
130
- cfTextColor: string;
131
- cfTextAlign: 'left' | 'center' | 'right';
132
- cfTextTransform: 'none' | 'capitalize' | 'uppercase' | 'lowercase';
133
- cfTextBold: boolean;
134
- cfTextItalic: boolean;
135
- cfTextUnderline: boolean;
136
- cfColumns: string;
137
- cfColumnSpan: string;
138
- cfColumnSpanLock: boolean;
139
- cfWrapColumns: boolean;
140
- cfWrapColumnsCount: string;
141
- };
142
- type CSSProperties = React.CSSProperties;
143
- type ContainerStyleVariableName = keyof StyleProps;
144
- type ExperienceFields = {
145
- title: string;
146
- slug: string;
147
- componentTree: ExperienceComponentTree;
148
- dataSource: ExperienceDataSource;
149
- unboundValues: ExperienceUnboundValues;
150
- usedComponents?: ExperienceUsedComponents | Array<ExperienceEntry>;
151
- componentSettings?: ExperienceComponentSettings;
152
- };
153
- type RecursiveDesignTokenDefinition = {
154
- [key: string]: string | RecursiveDesignTokenDefinition;
155
- };
156
- type DesignTokensDefinition = {
157
- spacing?: Record<string, string>;
158
- sizing?: Record<string, string>;
159
- color?: Record<string, string>;
160
- border?: Record<string, {
161
- width: string;
162
- style: 'solid' | 'dashed' | 'dotted';
163
- color: string;
164
- }>;
165
- borderRadius?: Record<string, string>;
166
- fontSize?: Record<string, string>;
167
- lineHeight?: Record<string, string>;
168
- letterSpacing?: Record<string, string>;
169
- textColor?: Record<string, string>;
170
- } & RecursiveDesignTokenDefinition;
171
- /** Type of experience entry JSON data structure as returned by CPA/CDA */
172
- type ExperienceEntry = {
173
- sys: Entry['sys'];
174
- fields: ExperienceFields;
175
- metadata: Entry['metadata'];
176
- };
177
- interface RawCoordinates {
178
- left: number;
179
- top: number;
180
- width: number;
181
- height: number;
182
- }
183
- interface Coordinates extends RawCoordinates {
184
- childrenCoordinates: RawCoordinates[];
185
- }
186
- interface HoveredElement {
187
- blockType: string | undefined;
188
- nodeId: string | undefined;
189
- blockId: string | undefined;
190
- }
191
- interface Experience<T extends EntityStore = EntityStore> {
192
- entityStore?: T;
193
- }
194
- type ResolveDesignValueType = (valuesByBreakpoint: ValuesByBreakpoint, variableName: string) => PrimitiveValue;
195
- type ManagementEntity = (Entry | Asset) & {
196
- sys: {
197
- version: number;
182
+ interface PortableTemplate {
183
+ templateId: string;
184
+ props: {
185
+ content: Record<string, unknown>;
186
+ design: Record<string, DesignPropValue>;
187
+ resolved?: Record<string, unknown>;
198
188
  };
199
- };
200
- type RequestEntitiesMessage = {
201
- entityIds: string[];
202
- entityType: 'Asset' | 'Entry';
203
- locale: string;
204
- };
205
- type RequestedEntitiesMessage = {
206
- entities: Array<Entry | Asset>;
207
- missingEntityIds?: string[];
208
- };
209
- type BoundComponentPropertyTypes = string | number | boolean | AssetFile | Record<string, AssetFile | undefined> | Document | OptimizedBackgroundImageAsset | OptimizedImageAsset | Link<'Asset'> | undefined;
210
- type OptimizedImageAsset = {
211
- url: string;
212
- srcSet?: string[];
213
- sizes?: string;
214
- quality?: number;
215
- format?: string;
216
- file: AssetFile;
217
- };
218
- type OptimizedBackgroundImageAsset = {
219
- url: string;
220
- srcSet?: string[];
221
- file: AssetFile;
222
- };
223
- type ImageObjectFitOption = 'contain' | 'cover' | 'none';
224
- type ImageObjectPositionOption = 'left' | 'right' | 'top' | 'bottom' | 'left top' | 'left center' | 'left bottom' | 'right top' | 'right center' | 'right bottom' | 'center top' | 'center center' | 'center bottom';
225
- type ImageOptions = {
226
- format?: string;
227
- width: string;
228
- height: string;
229
- objectFit?: ImageObjectFitOption;
230
- objectPosition?: ImageObjectPositionOption;
231
- quality?: string;
232
- targetSize: string;
233
- };
234
- type BackgroundImageScalingOption = 'fit' | 'fill' | 'tile';
235
- type BackgroundImageAlignmentOption = 'left' | 'right' | 'top' | 'bottom' | 'left top' | 'left center' | 'left bottom' | 'right top' | 'right center' | 'right bottom' | 'center top' | 'center center' | 'center bottom';
236
- type BackgroundImageOptions = {
237
- format?: string;
238
- scaling: BackgroundImageScalingOption;
239
- alignment: BackgroundImageAlignmentOption;
240
- quality?: string;
241
- targetSize: string;
242
- };
189
+ }
190
+ /**
191
+ * The interpreted experience tree.
192
+ *
193
+ * Top-level is `nodes: PortableRenderNode[]` (array, not single root) to
194
+ * match the actual XDA payload shape. Renderers iterate top-level nodes
195
+ * and recurse into `node.slots`. When `template` is present, the renderer
196
+ * wraps the nodes with the matching template config; otherwise nodes
197
+ * render at the top level.
198
+ */
199
+ interface PortableRenderPlan {
200
+ viewports: ViewportDef[];
201
+ nodes: PortableRenderNode[];
202
+ template?: PortableTemplate;
203
+ }
243
204
 
244
- export type { BackgroundImageAlignmentOption, BackgroundImageOptions, BackgroundImageScalingOption, Binding, BindingMap, BindingMapByBlockId, BoundComponentPropertyTypes, CSSProperties, ComponentBinding, ComponentDefinition, ComponentDefinitionVariable, ComponentDefinitionVariableBase, ComponentDefinitionVariableValidation, ComponentRegistration, ComponentRegistrationOptions, ContainerStyleVariableName, Coordinates, DataSourceEntryValueType, DesignTokensDefinition, Experience, ExperienceEntry, ExperienceFields, ExperienceTree, ExperienceTreeNode, ExternalSDKMode, HoveredElement, ImageObjectFitOption, ImageObjectPositionOption, ImageOptions, IncomingEvent, InternalEvent, InternalSDKMode, Link, ManagementEntity, OptimizedBackgroundImageAsset, OptimizedImageAsset, OutgoingEvent, RawCoordinates, RecursiveDesignTokenDefinition, RequestEntitiesMessage, RequestedEntitiesMessage, ResolveDesignValueType, ScrollState, StyleProps, ValidationOption, VariableFormats };
205
+ export type { ComponentTypeNode, ComponentTypeRef, DesignPropValue, DesignToken, ExperienceContext, ExperienceNode, ExperiencePayload, ExperienceSys, ManualDesignValue, PortableRegistration, PortableRenderNode, PortableRenderPlan, PortableTemplate, ResolveContext, TemplateNode, TemplateRef, ValuesByViewport, ViewportDef };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,75 +1,32 @@
1
1
  {
2
2
  "name": "@contentful/experiences-core",
3
- "version": "0.0.1",
4
- "description": "",
5
- "main": "dist/index.js",
6
- "module": "dist/index.js",
7
- "types": "dist/index.d.ts",
3
+ "version": "0.3.0",
4
+ "description": "Runtime-neutral types + experience resolution for Contentful Experiences",
5
+ "license": "MIT",
8
6
  "type": "module",
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/contentful/experience-builder.git"
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "import": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "./package.json": "./package.json"
12
18
  },
13
19
  "files": [
14
- "readme.md",
15
- "package.json",
16
- "dist/**/*.*"
20
+ "dist",
21
+ "README.md",
22
+ "CHANGELOG.md"
17
23
  ],
18
- "exports": {
19
- ".": "./dist/index.js",
20
- "./constants": "./dist/constants.js"
21
- },
22
24
  "publishConfig": {
23
- "registry": "https://npm.pkg.github.com/"
24
- },
25
- "typesVersions": {
26
- "*": {
27
- "types": [
28
- "./dist/types.d.ts"
29
- ],
30
- "constants": [
31
- "./dist/constants.d.ts"
32
- ]
33
- }
25
+ "access": "public"
34
26
  },
35
- "scripts": {
36
- "clean": "rimraf dist",
37
- "prebuild": "npm run clean",
38
- "build": "rollup -c ./rollup.config.mjs",
39
- "predev": "npm run clean",
40
- "dev": "rollup -c ./rollup.config.mjs --watch --environment DEV",
41
- "lint": "eslint src --ext '.ts,.tsx,.js,.jsx' --max-warnings 0 --ignore-path ../../.eslintignore",
42
- "lint:fix": "eslint src --ext '.ts,.tsx,.js,.jsx' --fix",
43
- "test": "vitest",
44
- "test:coverage": "vitest run --coverage",
45
- "depcruise": "depcruise src"
46
- },
47
- "author": "",
48
- "license": "MIT",
49
- "devDependencies": {
50
- "@rollup/plugin-commonjs": "^25.0.7",
51
- "@rollup/plugin-node-resolve": "^15.2.3",
52
- "@rollup/plugin-terser": "^0.4.4",
53
- "@rollup/plugin-typescript": "^11.1.5",
54
- "@types/lodash-es": "^4.17.12",
55
- "@typescript-eslint/eslint-plugin": "^7.0.2",
56
- "@typescript-eslint/parser": "^7.0.2",
57
- "@vitest/coverage-v8": "^1.0.4",
58
- "contentful": "^10.6.4",
59
- "eslint": "^8.54.0",
60
- "happy-dom": "^13.3.8",
61
- "rimraf": "^5.0.5",
62
- "rollup-plugin-dts": "^6.1.0",
63
- "rollup-plugin-ts": "^3.4.5",
64
- "vite-tsconfig-paths": "^4.2.2",
65
- "vitest": "^1.0.4"
66
- },
67
- "dependencies": {
68
- "@contentful/experiences-validators": "^0.0.1",
69
- "@contentful/rich-text-types": "^16.3.0"
70
- },
71
- "peerDependencies": {
72
- "contentful": ">=10.6.0"
73
- },
74
- "gitHead": "4b635ce38f276566da970c47ef8aec0cde771608"
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/contentful/experiences.git",
30
+ "directory": "packages/core"
31
+ }
75
32
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2023 Contentful
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,6 +0,0 @@
1
- import { PostMessageMethods } from '../constants.js';
2
- import { OutgoingEvent } from '../types.js';
3
-
4
- declare const sendMessage: (eventType: OutgoingEvent | PostMessageMethods, data?: unknown) => void;
5
-
6
- export { sendMessage };