@contentful/experiences-core 0.0.1 → 0.2.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 +9 -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 +117 -0
  8. package/dist/resolve-experience.js.map +1 -0
  9. package/dist/types.d.ts +191 -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};\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. v1 XDA\n // payloads don't yet emit template-level content/design properties, so\n // the IR carries empty bags — additive when the API grows them.\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 const experience: ExperienceContext = {\n ...DEFAULT_EXPERIENCE,\n ...options.experience,\n metadata: {\n ...DEFAULT_EXPERIENCE.metadata,\n ...(options.experience?.metadata ?? {}),\n },\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,200 @@
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. The conventional, single injection point — kept small
4
+ * at v1. Token resolution, locale, viewport metadata, analytics emitter,
5
+ * etc. land here in later iterations.
6
+ */
7
+ interface ExperienceContext {
8
+ isPreview: boolean;
9
+ metadata: Record<string, unknown>;
22
10
  }
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> = {
11
+ /**
12
+ * One viewport definition from a delivered Experience. The `query` is the
13
+ * Contentful media-query DSL ("*" | "<992px" | ">1200px"), not raw CSS.
14
+ *
15
+ * The first viewport in the list is conventionally the wildcard ("*") that
16
+ * always matches. The viewport order encodes the cascade direction —
17
+ * desktop-first (descending) or mobile-first (ascending).
18
+ */
19
+ interface ViewportDef {
43
20
  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;
21
+ query: string;
22
+ displayName: string;
23
+ previewSize: string;
24
+ }
25
+ /**
26
+ * Discriminated design-property value as it arrives from XDA. v1 accepts:
27
+ * - ManualDesignValue: an explicit scalar (no viewport involved).
28
+ * - ValuesByViewport: a viewport-keyed bag where each entry is itself a
29
+ * ManualDesignValue or DesignToken.
30
+ * - DesignToken: a token reference, passed through to customer components
31
+ * as-is for v1. Resolution lands in the future tokens package.
32
+ */
33
+ type DesignPropValue = ManualDesignValue | DesignToken | ValuesByViewport;
34
+ interface ManualDesignValue {
35
+ type: 'ManualDesignValue';
36
+ value: string | number | boolean;
37
+ }
38
+ interface DesignToken {
39
+ type: 'DesignToken';
40
+ value: string;
41
+ }
42
+ interface ValuesByViewport {
43
+ type: 'ValuesByViewport';
44
+ values: Record<string, ManualDesignValue | DesignToken>;
45
+ }
46
+ /**
47
+ * Resource-link reference to a registered Component Type. The `urn` carries
48
+ * the type id; the build-plan extracts the id by taking the segment after
49
+ * the last slash.
50
+ */
51
+ interface ComponentTypeRef {
52
+ sys: {
53
+ type: 'ResourceLink';
54
+ linkType: 'Contentful:ComponentType';
55
+ urn: string;
53
56
  };
54
- };
55
- type ComponentRegistration = {
56
- component: React.ElementType;
57
- definition: ComponentDefinition;
58
- options?: {
59
- wrapComponent?: boolean;
60
- wrapContainerTag?: keyof JSX.IntrinsicElements;
57
+ }
58
+ /**
59
+ * Resource-link reference to a Template. Templates are out of v1 scope and
60
+ * are skipped at plan-build time with a diagnostic.
61
+ */
62
+ interface TemplateRef {
63
+ sys: {
64
+ type: 'ResourceLink';
65
+ linkType: 'Contentful:Template';
66
+ urn: string;
61
67
  };
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[];
68
+ }
69
+ /**
70
+ * One node from `GetExperienceViewResponse.nodes` (or any `slots[name]`).
71
+ * Discriminated by which of `componentType` / `template` is present.
72
+ */
73
+ type ExperienceNode = ComponentTypeNode | TemplateNode;
74
+ interface ComponentTypeNode {
75
+ componentType: ComponentTypeRef;
76
+ id?: string;
77
+ contentProperties?: Record<string, unknown>;
78
+ designProperties?: Record<string, DesignPropValue>;
79
+ slots?: Record<string, ExperienceNode[]>;
80
+ contentBindings?: string;
81
+ }
82
+ interface TemplateNode {
83
+ template: TemplateRef;
84
+ id?: string;
85
+ contentProperties?: Record<string, unknown>;
86
+ designProperties?: Record<string, DesignPropValue>;
87
+ slots?: Record<string, ExperienceNode[]>;
88
+ contentBindings?: string;
89
+ }
90
+ /**
91
+ * Top-level `sys` block on an Experience payload. The bits the SDK actually
92
+ * reads are typed; everything else is left loose because the upstream
93
+ * type carries dozens of editor/audit fields the renderer doesn't care about.
94
+ */
95
+ interface ExperienceSys {
96
+ /**
97
+ * Optional page-level template reference. When present, the renderer wraps
98
+ * the experience nodes with the matching template registered in the
99
+ * customer's Config. When absent, nodes render at the top level.
100
+ */
101
+ template?: TemplateRef;
102
+ [key: string]: unknown;
103
+ }
104
+ /**
105
+ * Top-level Experience payload as returned by the Experience Delivery API
106
+ * (`GetExperienceViewResponse` from `@contentful/experience-delivery`).
107
+ *
108
+ * Structurally compatible with the upstream type — no normalization step
109
+ * required when consuming a delivery-client response.
110
+ */
111
+ interface ExperiencePayload {
112
+ viewports: ViewportDef[];
113
+ nodes: ExperienceNode[];
114
+ errors?: unknown[];
115
+ extensions?: unknown;
116
+ sys?: ExperienceSys;
117
+ }
118
+ /**
119
+ * Per-node context handed to a component's `resolveData` resolver. Carries
120
+ * the raw content + design props from the payload (design envelopes are NOT
121
+ * pre-resolved against a viewport — viewport resolution stays a render-time
122
+ * concern so client viewport changes don't re-trigger async resolvers).
123
+ */
124
+ interface ResolveContext {
125
+ content: Record<string, unknown>;
126
+ design: Record<string, DesignPropValue>;
127
+ experience: ExperienceContext;
128
+ }
129
+ /**
130
+ * Registration metadata for a single instance — the SDK's interpreted
131
+ * pointer to the customer's component implementation. v1 carries only the
132
+ * resolved component-type id; capabilities (state requirements, supported
133
+ * events, lifecycle hints, fallback ids) land on this object in later
134
+ * iterations without breaking the IR.
135
+ */
136
+ interface PortableRegistration {
137
+ componentTypeId: string;
138
+ }
139
+ /**
140
+ * The IR — one node per component instance. The seam that lets non-React
141
+ * adapters (Angular, SwiftUI, Compose) consume the same interpretation.
142
+ *
143
+ * Design props preserve the discriminated envelope as they arrived. Adapters
144
+ * unwrap to plain scalars at render time, given an active viewport.
145
+ * (DesignToken envelopes pass through unwrapped — customer components decide
146
+ * how to resolve them in v1.)
147
+ *
148
+ * `props.resolved` is populated by `resolveExperience` from any
149
+ * customer-supplied `resolveData` resolver and merged into the final prop bag
150
+ * after content + design but before slot props.
151
+ */
152
+ interface PortableRenderNode {
153
+ /**
154
+ * Optional. Passed through from the XDA payload's `id` field when the
155
+ * editor supplies one. The SDK does NOT auto-generate ids; adapters fall
156
+ * back to the array index for React keys / debug labels when absent.
157
+ */
158
+ nodeId?: string;
159
+ registration: PortableRegistration;
160
+ props: {
161
+ content: Record<string, unknown>;
162
+ design: Record<string, DesignPropValue>;
163
+ resolved?: Record<string, unknown>;
92
164
  };
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';
165
+ slots: Record<string, PortableRenderNode[]>;
166
+ }
102
167
  /**
103
- * Internally defined style variables are prefix with `cf` to avoid
104
- * collisions with user defined variables.
168
+ * Interpreted page-level template the optional wrapper around the
169
+ * experience tree. `templateId` is extracted from
170
+ * `payload.sys.template.sys.urn` (last slash-segment).
171
+ *
172
+ * Templates carry the same prop-resolution shape as components: content +
173
+ * design envelopes plus an optional `resolved` bag from a `resolveData` hook.
174
+ * v1 payloads from XDA don't carry template-level content/design properties
175
+ * yet, but the IR makes room for them so the API doesn't need to break later.
105
176
  */
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;
177
+ interface PortableTemplate {
178
+ templateId: string;
179
+ props: {
180
+ content: Record<string, unknown>;
181
+ design: Record<string, DesignPropValue>;
182
+ resolved?: Record<string, unknown>;
198
183
  };
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
- };
184
+ }
185
+ /**
186
+ * The interpreted experience tree.
187
+ *
188
+ * Top-level is `nodes: PortableRenderNode[]` (array, not single root) to
189
+ * match the actual XDA payload shape. Renderers iterate top-level nodes
190
+ * and recurse into `node.slots`. When `template` is present, the renderer
191
+ * wraps the nodes with the matching template config; otherwise nodes
192
+ * render at the top level.
193
+ */
194
+ interface PortableRenderPlan {
195
+ viewports: ViewportDef[];
196
+ nodes: PortableRenderNode[];
197
+ template?: PortableTemplate;
198
+ }
243
199
 
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 };
200
+ 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.2.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 };