Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .changeset/jss-10097-atoms-dl-chrome-metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@sitecore-content-sdk/react': patch
'@sitecore-content-sdk/nextjs': patch
---

[JSS-10097] Emit Design Library chrome metadata for low-code Atoms preview

- Wrap `DesignLibraryLowCodeComponent` with `PlaceholderMetadata` using the layout rendering UID
- Align READY/RENDERED status events with that same UID (same handshake as normal Design Library)
- Pass the editing-placeholder rendering from `DesignLibraryApp` into the low-code path
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { expect, use } from 'chai';
import sinonChai from 'sinon-chai';
import { render } from '@testing-library/react';
import { Page, PageMode } from '@sitecore-content-sdk/content/client';
import { LayoutServiceData } from '@sitecore-content-sdk/content/layout';
import { EDITING_COMPONENT_PLACEHOLDER, LayoutServiceData } from '@sitecore-content-sdk/content/layout';
import { DesignLibraryMode } from '@sitecore-content-sdk/content/editing';
import { ComponentMapEntry } from '../models';
import { getTestLayoutData } from '../../test-data/component-editing-data';
Expand Down Expand Up @@ -145,6 +145,9 @@ describe('<DesignLibraryApp />', () => {
render(awaitedDesignLibraryServer);

expect(DesignLibraryLowCodeComponentStub).to.have.been.calledOnce;
expect(DesignLibraryLowCodeComponentStub).to.have.been.calledWith({
rendering: layoutData.sitecore.route?.placeholders?.[EDITING_COMPONENT_PLACEHOLDER]?.[0],
});
expect(DesignLibraryStub).to.not.have.been.called;
expect(DesignLibraryServerStub).to.not.have.been.called;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const DesignLibraryApp = ({
return (
<>
{isLowCode ? (
<DesignLibraryLowCodeComponent />
<DesignLibraryLowCodeComponent rendering={rendering} />
) : isClient ? (
<DesignLibrary />
) : (
Expand Down
7 changes: 6 additions & 1 deletion packages/react/api/content-sdk-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,12 @@ export class DesignLibraryErrorBoundary extends React_2.Component<DesignLibraryE
}

// @internal
export const DesignLibraryLowCodeComponent: () => React_2.JSX.Element;
export const DesignLibraryLowCodeComponent: (input: DesignLibraryLowCodeComponentProps) => React_2.JSX.Element;

// @internal
export type DesignLibraryLowCodeComponentProps = {
rendering?: ComponentRendering;
};

export { DictionaryPhrases }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DesignLibraryStatus,
getDesignLibraryStatusEvent,
} from '@sitecore-content-sdk/content/editing';
import type { ComponentRendering } from '@sitecore-content-sdk/content/layout';
import type { AtomsConfig } from '../../atoms/types';
import type { ImportMapImport } from './models';
import type { DefineRegistryResult } from '@json-render/react';
Expand Down Expand Up @@ -56,6 +57,11 @@ describe('<DesignLibraryLowCodeComponent />', () => {
registry: mockRegistry,
};

const rendering: ComponentRendering = {
uid: 'test-content',
componentName: 'LowCodeComponent',
};

const getPage = () => ({
locale: 'en',
layout: { sitecore: { context: {}, route: null } },
Expand Down Expand Up @@ -86,7 +92,10 @@ describe('<DesignLibraryLowCodeComponent />', () => {
sandbox.restore();
});

const renderComponent = (runtime?: AtomsConfig) =>
const renderComponent = (
runtime?: AtomsConfig,
renderingProp: ComponentRendering | undefined = rendering
) =>
render(
<SitecoreProvider
api={apiStub}
Expand All @@ -95,19 +104,24 @@ describe('<DesignLibraryLowCodeComponent />', () => {
loadImportMap={loadImportMapStub}
atomsConfig={runtime}
>
<DesignLibraryLowCodeComponent />
<DesignLibraryLowCodeComponent rendering={renderingProp} />
</SitecoreProvider>
);

it('posts READY status on mount', async () => {
it('posts READY status on mount with rendering uid', async () => {
renderComponent(atomsConfig);
await waitFor(() => {
expect(postToDesignLibrarySpy).to.have.been.calledWith(
getDesignLibraryStatusEvent(DesignLibraryStatus.READY, 'low-code-component')
getDesignLibraryStatusEvent(DesignLibraryStatus.READY, 'test-content')
);
});
});

it('renders error when rendering uid is missing', () => {
const { container } = renderComponent(atomsConfig, { componentName: 'LowCodeComponent' });
expect(container.textContent).to.include('Rendering UID is missing in the rendering data');
});

it('sends error when no catalog is provided', async () => {
renderComponent(undefined);
await waitFor(() => {
Expand Down Expand Up @@ -151,11 +165,30 @@ describe('<DesignLibraryLowCodeComponent />', () => {

await waitFor(() => {
expect(postToDesignLibrarySpy).to.have.been.calledWith(
getDesignLibraryStatusEvent(DesignLibraryStatus.RENDERED, 'low-code-component')
getDesignLibraryStatusEvent(DesignLibraryStatus.RENDERED, 'test-content')
);
});
});

it('wraps preview with PlaceholderMetadata chromes using rendering uid', async () => {
const { container } = renderComponent(atomsConfig);

await waitFor(() => {
expect(addDocumentUpdateHandlerStub).to.have.been.called;
});

const openChrome = container.querySelector(
'code.scpm[chrometype="rendering"][kind="open"][id="test-content"]'
);
const closeChrome = container.querySelector(
'code.scpm[chrometype="rendering"][kind="close"]'
);

expect(openChrome).to.not.be.null;
expect(closeChrome).to.not.be.null;
expect(openChrome?.getAttribute('data-csdk-component-runtime')).to.equal('client');
});

it('subscribes to component props update handler', async () => {
renderComponent(atomsConfig);
await waitFor(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
'use client';
import React, { useEffect, useState } from 'react';
import { ComponentRendering } from '@sitecore-content-sdk/content/layout';
import { useSitecore } from '../SitecoreProvider';
import { serializeCatalog } from '../../atoms';
import { StudioComponentWrapper } from './StudioComponentWrapper';
import type { Document } from '@sitecore-content-sdk/content/atoms';
import * as editing from '@sitecore-content-sdk/content/editing';
import * as atoms from '@sitecore-content-sdk/content/atoms';
import { DesignLibraryErrorBoundary } from '../..';
import { DesignLibraryErrorBoundary, PlaceholderMetadata } from '../..';
import { ErrorComponent } from '../ErrorBoundary';
import type { ChildComponentProps } from '../Placeholder/models';

let { postToDesignLibrary, getDesignLibraryStatusEvent, DesignLibraryStatus } = editing;
Expand All @@ -32,28 +34,49 @@ export const __mockDependencies = (mocks: any) => {
}
};

/**
* Props for {@link DesignLibraryLowCodeComponent}.
* @internal
*/
export type DesignLibraryLowCodeComponentProps = {
/**
* Sitecore rendering from the Design Library editing placeholder.
* UID must match READY/RENDERED status events and chrome metadata (same contract as DesignLibrary).
*/
rendering?: ComponentRendering;
};

/**
* Design Library Low Code component.
*
* Facilitates the communication between the Design Studio and the Rendering Host when previewing a low code component built with the Atoms.
* - On mount, it serializes the atoms catalog and sends it to the Design Studio via the `atoms:catalog` event.
* - Receives Component model data updates via document update handler and renders the low code component
* via `StudioComponentWrapper` (same client path as Studio / NCC preview elsewhere).
* - Wraps preview output with `PlaceholderMetadata` using the layout rendering UID so Design Studio
* receives the same chrome handshake as normal Design Library components.
* @param {DesignLibraryLowCodeComponentProps} props - Component props.
* @returns {JSX.Element} The low-code preview surface.
* @internal
*/
export const DesignLibraryLowCodeComponent = () => {
export const DesignLibraryLowCodeComponent = ({ rendering }: DesignLibraryLowCodeComponentProps) => {
const { atomsConfig } = useSitecore();
const uid = rendering?.uid;
const [currentDocument, setCurrentDocument] = useState<Document | null>(null);
const [renderKey, setRenderKey] = useState(0);
const [fields, setFields] = useState<ChildComponentProps['fields'] | undefined>();
const [params, setParams] = useState<ChildComponentProps['params'] | undefined>();

if (!uid) {
return <ErrorComponent message="Rendering UID is missing in the rendering data" />;
}

// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
postToDesignLibrary(
getDesignLibraryStatusEvent(DesignLibraryStatus.READY, 'low-code-component')
);
}, []);
postToDesignLibrary(getDesignLibraryStatusEvent(DesignLibraryStatus.READY, uid));
}, [uid]);

// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
const unsubscribe = addComponentPropsUpdateHandler((updatedFields, updatedParams) => {
setFields(updatedFields as ChildComponentProps['fields']);
Expand All @@ -62,6 +85,7 @@ export const DesignLibraryLowCodeComponent = () => {
return () => unsubscribe();
}, []);

// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (!atomsConfig?.catalog) {
sendAtomsErrorEvent('No atoms catalog provided', 'atoms-missing');
Expand All @@ -79,20 +103,18 @@ export const DesignLibraryLowCodeComponent = () => {
return () => unsubDocumentUpdate();
}, [atomsConfig]);

// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (renderKey === 0) return;

postToDesignLibrary(
getDesignLibraryStatusEvent(DesignLibraryStatus.RENDERED, 'low-code-component')
);
}, [renderKey]);
postToDesignLibrary(getDesignLibraryStatusEvent(DesignLibraryStatus.RENDERED, uid));
}, [renderKey, uid]);

return (
<DesignLibraryErrorBoundary
uid={currentDocument?.name ?? 'design-library-low-code-component'}
renderKey={renderKey}
>
<StudioComponentWrapper document={currentDocument} fields={fields} params={params} />
<DesignLibraryErrorBoundary uid={uid} renderKey={renderKey}>
<PlaceholderMetadata rendering={rendering!} componentRuntime="client">
<StudioComponentWrapper document={currentDocument} fields={fields} params={params} />
</PlaceholderMetadata>
</DesignLibraryErrorBoundary>
);
};
5 changes: 4 additions & 1 deletion packages/react/src/components/DesignLibrary/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export { DesignLibrary } from './DesignLibrary';
export { DesignLibraryLowCodeComponent } from './DesignLibraryLowCodeComponent';
export {
DesignLibraryLowCodeComponent,
DesignLibraryLowCodeComponentProps,
} from './DesignLibraryLowCodeComponent';
export { DesignLibraryErrorBoundary } from './DesignLibraryErrorBoundary';
export { noopLoadImportMap } from './loadImportMap';
export { DynamicComponent, ImportMapImport } from './models';
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export {
export {
DesignLibrary,
DesignLibraryLowCodeComponent,
DesignLibraryLowCodeComponentProps,
DesignLibraryErrorBoundary,
DynamicComponent,
ImportMapImport,
Expand Down
Loading