/**
* Emit a Sitecore layout XML string from a structural `Layout` shape.
*
* Sitecore stores layout as a single XML string in the `__Renderings`
* (shared) or `__Final Renderings` (versioned) field on any item that
* holds a layout — pages, partial designs, page designs. This emitter
* is the single seam that turns recipe-level structural layout into
* that wire form. The output is deterministic given the same inputs:
* placement UIDs derive from the parent item ID + placeholder + index +
* componentHandle, so re-pushes produce identical XML.
*
* Wire format:
*
*
*
*
* ...
*
*
*
* The placeholder attribute is `ph` (`s:ph` in delta form) — Sitecore's
* layout engine reads exactly that name and silently ignores anything
* else. An earlier iteration wrote `placeh`, which Sitecore stored
* verbatim but never bound to a placeholder, so pushed pages rendered
* empty until a first save in Pages rewrote the XML.
*
* Recipe → wire mapping:
*
* componentHandle → id (renderingId(handle), curly-uppercase)
* placeholder key (dict key) → ph
* datasourceRef.kind=shared → ds (contentItemId(handle), curly-uppercase)
* datasourceRef.kind=scoped → REJECTED for partial/page designs
* (no host page to resolve against)
* datasourceRef.kind=none → no ds attribute
* variant → par (FieldNames=, URL-encoded)
* params → par (key=value pairs, URL-encoded)
* (parentItemId, placeholder, index, componentHandle) → uid (uuidv5)
*/
export interface ComponentPlacementInput {
componentHandle: string;
variant?: string;
params?: Record;
datasourceRef?: {
kind: "shared";
handle: string;
} | {
kind: "scoped";
slot: string;
} | {
kind: "none";
};
/**
* Nested placements (schema-legal on any placement). The emitter
* only consumes FLAT layouts — the page, partial-design, and
* page-design compilers flatten the tree into concrete
* dynamic-placeholder keys before emission
* (`compile/flatten-layout.ts`). A placement reaching this emitter
* with children still attached comes from a layout context without a
* flattening pass (a content-item version layout, a page-template
* standard-values layout). Rejected loudly rather than silently
* dropped.
*/
placeholders?: Record;
}
export interface LayoutInput {
placeholders: Record;
}
export interface LayoutEmitContext {
/**
* The Sitecore item ID this layout will live on (partial-design or
* page-design item). Seeds deterministic placement UIDs.
*/
parentItemId: string;
/** Sitecore device GUID. Caller passes `DEFAULT_DEVICE_ID` from sitecore-templates.ts. */
deviceId: string;
/** Maps a component recipe handle to its rendering item GUID. */
renderingIdFor: (handle: string) => string;
/** Maps a content-item recipe handle to its content item GUID. */
contentItemIdFor: (handle: string) => string;
/**
* Whether `kind: "scoped"` datasource refs are permitted. False for
* partial-design and page-design layouts (they don't have a host page
* to resolve scoped refs against); `PageRecipe` sets it true.
*/
allowScoped: boolean;
/**
* Resolver for `kind: "scoped"` datasource refs — maps a placement's
* `slot` to the GUID of the page-local datasource item the caller
* materialises at `/Data/`; the scoped `` element then
* carries `ds="{resolvedGuid}"`.
*
* When `allowScoped` is true but this is absent, a scoped ref emits
* the page-relative `ds="local:/Data/"` form — the wire form
* XM Cloud Pages itself writes for page-local datasources (resolved
* against the context item at render time, and re-pointed for free
* when a page is copied). `PageRecipe` layouts use this form; the
* GUID resolver remains for callers that need absolute references.
*/
scopedDatasourceIdFor?: (slot: string) => string;
/**
* Wire form for the emitted XML.
*
* - `"canonical"` (default) — ``. What our recipe inputs naturally describe.
* Page Design items round-trip this byte-for-byte.
*
* - `"delta"` — the SXA delta wire form: ``, merged over the base layout (a page's template
* standard values; a partial design's inherited layout) by
* Sitecore's XmlDeltas at read time. This is the form XM Cloud
* Pages writes to `__Final Renderings` on every author save, so
* emitting it directly means the first push round-trips and
* converges in one cycle. Page Design layouts must NOT use this
* mode (they preserve canonical and would diverge).
*/
mode?: "canonical" | "delta";
/**
* Whether delta mode emits the `` device-attributes
* directive as the first child of ``. Defaults to true — the form
* the SXA Partial Design pipeline round-trips. Page `__Final
* Renderings` deltas must pass false: operator-verified Pages-authored
* deltas never carry the directive, and the page inherits its
* `l="{JSON layout}"` pointer from the template's standard values.
* Ignored in canonical mode.
*/
deltaDeviceDirective?: boolean;
/**
* Whether delta mode emits per-placement position anchors
* (`p:before="*"` / `p:after="…"`). Defaults to true — the SXA
* Partial Design pipeline's form, also what page `__Final
* Renderings` deltas carry. Page `__Renderings` (SHARED layout,
* `layoutScope: "shared"`) must pass false: operator-verified
* Pages-authored shared layouts carry anchor-less `` elements
* (placement order is document order), plus a root-level
* `` directive — pass `deltaSharedForm` to emit
* that too. Anchored deltas against the (typically empty) template
* standard-values base fail to place. Ignored in canonical mode.
*/
deltaAnchors?: boolean;
/**
* Emit the `` root directive as the first child of
* the delta root — the form XM Cloud Pages writes for the SHARED
* `__Renderings` delta. Defaults to false. Ignored in canonical mode.
*/
deltaSharedForm?: boolean;
/**
* Optional resolver mapping (componentHandle, variantName) to the
* headless Variant Definition item's refKey GUID. When it returns a
* GUID, the placement's `FieldNames` rendering parameter carries the
* curly-braced GUID — the form XM Cloud Pages writes, which its
* variant picker displays as the selection and the layout service
* resolves back to the variant item's NAME for the front end's
* export lookup. When absent (or when it returns undefined — e.g.
* the component recipe doesn't declare the variant, so no Variant
* Definition item exists to reference), `FieldNames` falls back to
* the raw variant name, which the layout service passes through
* unresolved and the front end matches by name directly.
*/
variantRefFor?: (componentHandle: string, variantName: string) => string | undefined;
/**
* Optional per-param wire-value mapper. Called for every rendering
* parameter (except the reserved `DynamicPlaceholderId` and
* `FieldNames`) with the placement's componentHandle, the param name,
* and the recipe-level raw value; a returned string REPLACES the raw
* value in the emitted `par` blob. `PageRecipe` uses this to store
* enum-backed Droplink values as enum-value item GUIDs and checkbox
* values as `1`/`""` — the forms XM Cloud Pages' properties panel
* reads back. Returning undefined keeps the raw value.
*/
paramValueFor?: (componentHandle: string, paramName: string, rawValue: string) => string | undefined;
/**
* SXA JSON Layout definition GUID. When set, the device element
* carries an `l="{layoutId}"` attribute — `…` — and `emitLayoutXml` emits the wrapper shell
* even when there are zero placements (a page-template `__Standard
* Values` layout: device + JSON-layout pointer, no renderings). Used
* only in `"canonical"` mode; `"delta"` ignores it. When unset, the
* device element has no `l` attribute (partial / page-design layouts).
*/
layoutId?: string;
}
/**
* Emit the layout XML for a `Layout` block. Returns an empty string
* when the layout has no placements — caller decides whether to write
* an empty `__Renderings` field or skip it.
*
* Mode selection (see `LayoutEmitContext.mode` JSDoc):
* - `"canonical"` (default) — what page-design items round-trip cleanly.
* - `"delta"` — SXA partial-design wire form so first push converges.
*
* Throws when a `kind: "scoped"` datasourceRef appears and
* `ctx.allowScoped` is false — layout contexts without an item to host
* a `Data` folder (content-item version layouts, page-template
* standard-values layouts) reject scoped refs.
*/
export declare function emitLayoutXml(layout: LayoutInput, ctx: LayoutEmitContext): string;