feat(sdk-angular): accessible semantic block editor renderer#35855
Conversation
Adds DotCMSBlockEditorRendererNativeComponent (<dotcms-block-editor-renderer-native>), which renders Block Editor content as clean semantic HTML (<ul><li><p>…</p></li></ul>) with no custom wrapper elements between semantic tags. Wrapper elements between <ul> and <li> broke the list → listitem relationship required by the HTML spec and assistive technology, and were flagged by a11y scanners. The native renderer makes the host element the real semantic tag (attribute-selector hosts) and recurses through ng-template outlets that render as comment nodes, so <li> stays a true DOM child of <ul>. It exposes the identical public input API and customRenderers contract, so migration is a one-line swap of the tag and import. The original DotCMSBlockEditorRendererComponent is marked @deprecated; its DOM output is unchanged for backward compatibility. README, MIGRATION, and CHANGELOG (v1.5.4) document the new component and the deprecation. Both Angular examples (CSR + SSR) updated to use it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ments The native renderer's TEXT case reused the component-based text renderer, which emitted a <dotcms-block-editor-renderer-text> host element per text node and per nested mark. A run with stacked marks produced a chain of wrapper elements around the actual <strong>/<u>/... tags. Inline the text + marks rendering into the dispatch template via a self-recursive #textRun ng-template (rendered as comment nodes), so a marked run is now exactly <p><u><strong>Few</strong></u></p> with no wrapper elements. Also binds the link mark's [class] attribute, which the old text renderer computed but never applied. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a custom renderer for the built-in bulletList block to both the angular and angular-ssr blog examples, wired into customRenderers as `bulletList`. It renders a <ul> with hot-pink oversized bullets and delegates each item's content back to the native renderer, demonstrating that customRenderers (overriding a default block type) works with DotCMSBlockEditorRendererNativeComponent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lock The bulletList custom renderer had to re-render its children through the native renderer, which added a wrapper element inside each <li> — the very nesting this renderer removes — making it a poor example. Replace it with a custom `blockquote` renderer that renders self-contained markup (a 💡 callout card) from the node's flattened text, with no child re-rendering and no wrapper elements. This is the natural custom-block use case. Applied to both the angular and angular-ssr blog examples. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the callout component's inline styles block with Tailwind utility classes, matching the styling approach used across the blog examples. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Claude finished @fmontes's task in 5m 2s —— View job ReviewA few real concerns worth a second look. None are blocking on their own; #1, #3, #4 are the ones I'd most want resolved (or explicitly accepted) before this ships as a published SDK component pitched as the a11y-correct replacement. 1. Reverse-tabnabbing still possible when author supplies
|
There was a problem hiding this comment.
Pull request overview
Introduces DotCMSBlockEditorRendererNativeComponent, an accessible successor to the existing Angular Block Editor renderer that emits semantic HTML (e.g. <ul><li><p>…</p></li></ul>) with no custom wrapper elements between semantic tags. The legacy renderer is preserved and marked @deprecated. The component reuses existing media/table/grid/contentlet/unknown blocks and uses attribute-selector components plus ng-template/ngTemplateOutlet recursion so list-to-listitem DOM relationships are preserved for assistive technology.
Changes:
- Add
DotCMSBlockEditorRendererNativeComponentand internal attribute-selector semantic components, exported from the SDKpublic_api.ts. - Mark the existing
DotCMSBlockEditorRendererComponentas deprecated; update README/MIGRATION/CHANGELOG (v1.5.4) to describe the migration. - Migrate the CSR and SSR Angular examples to the new component and add a self-contained
CalloutComponentcustom renderer demo.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| core-web/libs/sdk/angular/src/public_api.ts | Export the new native renderer component from the SDK. |
| core-web/libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer-semantic/dotcms-block-editor-renderer-native.component.ts | New component class with identical public API to legacy and same validation flow. |
| core-web/libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer-semantic/dotcms-block-editor-renderer-native.component.html | Template that dispatches nodes to semantic tags via ng-template/ngTemplateOutlet and renders marks as native inline tags. |
| core-web/libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer-semantic/blocks/semantic-blocks.component.ts | Internal attribute-selector components keeping semantic tags as DOM hosts. |
| core-web/libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer-semantic/dotcms-block-editor-renderer-native.component.spec.ts | Unit tests for init, validation, class/style passthrough, and the asLevel helper. |
| core-web/libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer-semantic/dotcms-block-editor-renderer-native.dispatch.spec.ts | Dispatch-level tests covering semantic DOM output, mark nesting, lists, headings, media, and custom renderers. |
| core-web/libs/sdk/angular/src/lib/components/dotcms-block-editor-renderer/dotcms-block-editor-renderer.component.ts | Adds @deprecated JSDoc pointing to the native renderer. |
| core-web/libs/sdk/angular/README.md | Documents new component; deprecation notice on legacy section. |
| core-web/libs/sdk/angular/MIGRATION.md | Adds migration section with one-line swap diff. |
| core-web/libs/sdk/angular/CHANGELOG.md | Adds v1.5.4 entry. |
| examples/angular/.../blog-post.component.{ts,html} | Switches CSR example to the native component and registers a callout custom renderer. |
| examples/angular/.../customRenderers/callout/callout.component.ts | New self-contained CSR callout custom renderer. |
| examples/angular-ssr/.../blog-post.component.{ts,html} | Switches SSR example to the native component and registers the callout. |
| examples/angular-ssr/.../customRenderers/callout/callout.component.ts | New SSR callout custom renderer (mirror of CSR). |
The DotCMSBlockEditorRendererNative input table listed types that don't match the component: blocks is BlockEditorNode (not BlockEditorContent), and style is string | Record<string, string> (not CSSProperties). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Address code-review findings on the new native renderer's template: - Link marks now bind href/target with `|| null` so missing attrs omit the attribute instead of emitting `href=""` (a self-link) / `target=""`. - Unknown mark types recurse through `restMarks` instead of falling straight to text, so known marks beneath them (e.g. `[unknown, bold]`) are preserved. - Paragraph and heading hosts bind `[style.text-align]` explicitly instead of the whole `node.attrs` object, so non-CSS attrs like heading `level` no longer leak onto the style attribute. Adds dispatch tests for each case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the thorough pass. Addressed the three actionable template findings in
Deferring the two signal/lifecycle items as a focused follow-up rather than folding them in here:
OnPush on the host and the dispatch-test fixtures ( |
Apply the worth-fixing items from the PR review: - Hrefless link marks no longer emit an <a>: a hrefless anchor isn't a link to assistive tech (no role, not focusable), so we fall back to recursing into the remaining marks/text instead. - Unknown / missing heading levels render as <h2>, not <h1>: an unexpected <h1> in an article tanks the heading outline AT relies on. asLevel() now only normalizes to "1"–"6"; anything else falls through to the safe @default. - DotCMSBlockEditorRendererNativeComponent declares `implements OnInit`, marks BLOCKS as `protected readonly`, and drops a redundant cast in markAttrs(). - README acknowledges that table/grid cells still go through the legacy block component internally (a follow-up). - MIGRATION notes the style-passthrough behavior change: the native renderer only forwards `textAlign`; other inline-style attrs that the legacy renderer copied through `[style]="node.attrs"` are dropped. - Deprecated-section input table updated to the current types (BlockEditorNode, `class` instead of `className`, `string | Record<string, string>` instead of CSSProperties). Tests cover hrefless link → no <a>, inner marks surviving beneath a dropped link, the <h2> default, and the new asLevel range checks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the careful review. Here's what I addressed and what I deliberately didn't, with reasoning: Fixed in
Deliberately not changing:
Deferred (per your #8): I'll open a follow-up issue covering Verified: 211 unit tests pass, lint clean, build emits the export. |
The deprecation info already lives in the component's TSDoc (and the migration steps are in MIGRATION.md), so the duplicated README section is just maintenance overhead. Drop it, drop its TOC entry, and replace the cross-references in the native renderer section with a single short note + link to MIGRATION.md. Also tightens the native section's prose since the "Why migrate" framing no longer makes sense without the deprecated section above it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t=_blank - Table @for loops tracked by node.type ('tableRow'/'tableCell'), so all iterations shared the same key and Angular's keyed diff reused the wrong DOM node on updates. Switched both header and body loops to track $index. - <a target="_blank"> with no author-supplied rel is vulnerable to reverse-tabnabbing. The renderer now defaults rel to 'noopener noreferrer' in that case; an explicit rel still wins. - Added dispatch specs covering both fixes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the project's Angular standard for new components and is the natural fit for a signal-input component. 217 unit tests still pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
columnSpan returned undefined when `content` had more columns than `attrs.columns` described, rendering as `grid-column: span undefined`. Falls back to 6 (the documented behavior) and the JSDoc now states it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A float-wrapped <figure> sets width: 50% on the wrapper, but the <img> inside had no constraint, so wide images rendered at intrinsic size and overlapped surrounding text. max-width: 100%; height: auto keeps the image scaled to the wrapper while preserving its aspect ratio. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What
Adds
DotCMSBlockEditorRendererNativeComponent(<dotcms-block-editor-renderer-native>), an accessible successor to the block editor renderer that emits clean semantic HTML —<ul><li><p>…</p></li></ul>— with no custom wrapper elements between semantic tags. The originalDotCMSBlockEditorRendererComponentis marked@deprecatedwith its output unchanged for backward compatibility.Why
An a11y scanner flagged the existing renderer for excessive nested wrapper elements: a dispatcher element sat between
<ul>and its<li>children, breaking thelist → listitemrelationship required by the HTML spec and assistive technology. This is a published SDK, so rather than change existing output we add a new opt-in component.How
ng-templateoutlets that render as comment nodes, so semantic nesting stays intact.<strong>/<u>/<a>/… with no wrapper per text node.customRendererscontract — migration is a one-line swap of tag and import.Notes
<td>/grid cells, not an ARIA-strict context).🤖 Generated with Claude Code
This PR fixes: #35852