From 9dfdfcdcc99081bc01672a924e5394ae0ac55cb6 Mon Sep 17 00:00:00 2001 From: MenKNas Date: Wed, 29 Jul 2026 11:59:02 +0300 Subject: [PATCH 1/2] [Atoms] Add metadata wrapper for low code component --- .../jss-10097-atoms-dl-chrome-metadata.md | 10 ++++ .../DesignLibrary/DesignLibraryApp.test.tsx | 5 +- .../DesignLibrary/DesignLibraryApp.tsx | 2 +- .../DesignLibraryLowCodeComponent.test.tsx | 43 +++++++++++++++-- .../DesignLibraryLowCodeComponent.tsx | 48 +++++++++++++------ 5 files changed, 86 insertions(+), 22 deletions(-) create mode 100644 .changeset/jss-10097-atoms-dl-chrome-metadata.md diff --git a/.changeset/jss-10097-atoms-dl-chrome-metadata.md b/.changeset/jss-10097-atoms-dl-chrome-metadata.md new file mode 100644 index 0000000000..2ae945a469 --- /dev/null +++ b/.changeset/jss-10097-atoms-dl-chrome-metadata.md @@ -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 diff --git a/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.test.tsx b/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.test.tsx index 55f514b86b..8eb43729e9 100644 --- a/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.test.tsx +++ b/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.test.tsx @@ -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'; @@ -145,6 +145,9 @@ describe('', () => { 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; }); diff --git a/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.tsx b/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.tsx index 6e9bd4fdf7..58a077ad47 100644 --- a/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.tsx +++ b/packages/nextjs/src/components/DesignLibrary/DesignLibraryApp.tsx @@ -35,7 +35,7 @@ export const DesignLibraryApp = ({ return ( <> {isLowCode ? ( - + ) : isClient ? ( ) : ( diff --git a/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.test.tsx b/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.test.tsx index 342c46a51d..7721b59e34 100644 --- a/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.test.tsx +++ b/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.test.tsx @@ -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'; @@ -56,6 +57,11 @@ describe('', () => { registry: mockRegistry, }; + const rendering: ComponentRendering = { + uid: 'test-content', + componentName: 'LowCodeComponent', + }; + const getPage = () => ({ locale: 'en', layout: { sitecore: { context: {}, route: null } }, @@ -86,7 +92,10 @@ describe('', () => { sandbox.restore(); }); - const renderComponent = (runtime?: AtomsConfig) => + const renderComponent = ( + runtime?: AtomsConfig, + renderingProp: ComponentRendering | undefined = rendering + ) => render( ', () => { loadImportMap={loadImportMapStub} atomsConfig={runtime} > - + ); - 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(() => { @@ -151,11 +165,30 @@ describe('', () => { 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(() => { diff --git a/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx b/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx index 1ecc554b48..1dd9c23682 100644 --- a/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx +++ b/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx @@ -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; @@ -32,6 +34,14 @@ export const __mockDependencies = (mocks: any) => { } }; +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. * @@ -39,21 +49,30 @@ export const __mockDependencies = (mocks: any) => { * - 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(null); const [renderKey, setRenderKey] = useState(0); const [fields, setFields] = useState(); const [params, setParams] = useState(); + if (!uid) { + return ; + } + + // 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']); @@ -62,6 +81,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'); @@ -79,20 +99,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 ( - - + + + + ); }; From eb32027fb37bad20fdb344464f4f6fad5343080d Mon Sep 17 00:00:00 2001 From: MenKNas Date: Wed, 29 Jul 2026 17:44:27 +0300 Subject: [PATCH 2/2] Fix jsdocs/api verify issues --- packages/react/api/content-sdk-react.api.md | 7 ++++++- .../DesignLibrary/DesignLibraryLowCodeComponent.tsx | 4 ++++ packages/react/src/components/DesignLibrary/index.ts | 5 ++++- packages/react/src/index.ts | 1 + 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/react/api/content-sdk-react.api.md b/packages/react/api/content-sdk-react.api.md index b66466e1a5..7ce586f1f7 100644 --- a/packages/react/api/content-sdk-react.api.md +++ b/packages/react/api/content-sdk-react.api.md @@ -274,7 +274,12 @@ export class DesignLibraryErrorBoundary extends React_2.Component React_2.JSX.Element; +export const DesignLibraryLowCodeComponent: (input: DesignLibraryLowCodeComponentProps) => React_2.JSX.Element; + +// @internal +export type DesignLibraryLowCodeComponentProps = { + rendering?: ComponentRendering; +}; export { DictionaryPhrases } diff --git a/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx b/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx index 1dd9c23682..14a81b62cc 100644 --- a/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx +++ b/packages/react/src/components/DesignLibrary/DesignLibraryLowCodeComponent.tsx @@ -34,6 +34,10 @@ export const __mockDependencies = (mocks: any) => { } }; +/** + * Props for {@link DesignLibraryLowCodeComponent}. + * @internal + */ export type DesignLibraryLowCodeComponentProps = { /** * Sitecore rendering from the Design Library editing placeholder. diff --git a/packages/react/src/components/DesignLibrary/index.ts b/packages/react/src/components/DesignLibrary/index.ts index d997f93cf1..c9b4a49799 100644 --- a/packages/react/src/components/DesignLibrary/index.ts +++ b/packages/react/src/components/DesignLibrary/index.ts @@ -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'; diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 30f41bc200..50050b270b 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -82,6 +82,7 @@ export { export { DesignLibrary, DesignLibraryLowCodeComponent, + DesignLibraryLowCodeComponentProps, DesignLibraryErrorBoundary, DynamicComponent, ImportMapImport,