fix(builder): emit Container cssId/cssClass with live panel controls (D1a)

Container.craft.props collected cssId/cssClass since before Phase E1 but
nothing rendered or exported them. Add live "CSS ID" / "CSS Class" text
inputs to ContainerStylePanel (guarded on nodeProps.cssId/cssClass
!== undefined) and emit id=/class= in both the editor render and toHtml.
cssId takes precedence over the existing anchorId prop when both are set
(only one id attribute can be emitted); anchorId is used as a fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-12 13:46:23 -07:00
parent cf56f2a388
commit 36ce256760
3 changed files with 86 additions and 4 deletions
+17 -4
View File
@@ -37,6 +37,8 @@ export const Container: UserComponent<ContainerProps> = ({
fullWidth = false,
contentWidth = 'full',
anchorId,
cssId,
cssClass,
}) => {
const { connectors: { connect, drag } } = useNode();
@@ -50,13 +52,19 @@ export const Container: UserComponent<ContainerProps> = ({
...(needsBoxedWrapper ? {} : flexStyles),
};
// cssId is the user-facing "CSS ID" advanced field; it takes precedence
// over anchorId (the scroll-jump anchor) when both happen to be set, since
// only one `id` attribute can be emitted on the element.
const idValue = cssId || anchorId || undefined;
const el = React.createElement(
tag,
{
ref: (ref: HTMLElement | null): void => { if (ref) connect(drag(ref)); },
style: outerStyle,
'data-craft-container': 'true',
id: anchorId || undefined,
id: idValue,
className: cssClass || undefined,
},
needsBoxedWrapper
? React.createElement('div', { style: { maxWidth: '1200px', margin: '0 auto', ...flexStyles } }, children)
@@ -76,6 +84,8 @@ Container.craft = {
fullWidth: false,
contentWidth: 'full',
anchorId: '',
cssId: '',
cssClass: '',
},
rules: {
canDrag: () => true,
@@ -102,12 +112,15 @@ Container.craft = {
}
const styleStr = cssPropsToString(outerCss);
const idAttr = props.anchorId ? ` id="${escapeAttr(props.anchorId)}"` : '';
// cssId wins over anchorId when both are set (see the render fn above for why).
const idValue = props.cssId || props.anchorId;
const idAttr = idValue ? ` id="${escapeAttr(idValue)}"` : '';
const classAttr = props.cssClass ? ` class="${escapeAttr(props.cssClass)}"` : '';
if (isBoxed) {
const innerStyle = cssPropsToString({ maxWidth: '1200px', margin: '0 auto', ...flexStyles });
return { html: `<${tag}${idAttr}${styleStr ? ` style="${styleStr}"` : ''}><div${innerStyle ? ` style="${innerStyle}"` : ''}>${childrenHtml}</div></${tag}>` };
return { html: `<${tag}${idAttr}${classAttr}${styleStr ? ` style="${styleStr}"` : ''}><div${innerStyle ? ` style="${innerStyle}"` : ''}>${childrenHtml}</div></${tag}>` };
}
return { html: `<${tag}${idAttr}${styleStr ? ` style="${styleStr}"` : ''}>${childrenHtml}</${tag}>` };
return { html: `<${tag}${idAttr}${classAttr}${styleStr ? ` style="${styleStr}"` : ''}>${childrenHtml}</${tag}>` };
};