import { LitElement, html, css } from 'lit'; import { customElement, property } from 'lit/decorators.js'; // Import official USWDS compiled CSS import '../../styles/styles.css'; export interface ProseContentItem { type: 'paragraph' | 'heading' | 'list' | 'blockquote' | 'code'; level?: number; content: string; items?: string[]; } export interface ProseDetail { content: string; variant: string; } /** * USA Prose Web Component * * Provides USWDS-styled content blocks for rich text and document formatting. * Applies consistent typography, spacing, and styling to various content types. * Perfect for documentation, articles, and formatted text content. * * @element usa-prose * * @see README.mdx - Complete API documentation, usage examples, and implementation notes * @see CHANGELOG.mdx - Component version history and breaking changes * @see TESTING.mdx - Testing documentation and coverage reports * * @uswds-css-reference https://github.com/uswds/uswds/tree/develop/packages/usa-prose/src/styles/_usa-prose.scss * @uswds-docs https://designsystem.digital.gov/components/prose/ * @uswds-guidance https://designsystem.digital.gov/components/prose/#guidance * @uswds-accessibility https://designsystem.digital.gov/components/prose/#accessibility */ @customElement('usa-prose') export class USAProse extends LitElement { static override styles = css` :host { display: block; } `; @property({ type: String }) variant: 'default' | 'condensed' | 'expanded' = 'default'; @property({ type: String }) width: 'default' | 'narrow' | 'wide' = 'default'; @property({ type: String }) content = ''; private slottedContent: string = ''; // Use light DOM for USWDS compatibility protected override createRenderRoot(): HTMLElement { return this as any; } override connectedCallback() { super.connectedCallback(); // Set web component managed flag to prevent USWDS auto-initialization conflicts this.setAttribute('data-web-component-managed', 'true'); // Capture any initial content before render if (this.childNodes.length > 0 && !this.content) { this.slottedContent = this.innerHTML; this.innerHTML = ''; } } override updated(changedProperties: Map) { super.updated(changedProperties); // Apply captured content using DOM manipulation (avoids unsafeHTML directive issues) this.applySlottedContent(); this.applyContentProperty(); } private applySlottedContent() { if (this.slottedContent && !this.content) { const container = this.querySelector('.usa-prose'); if (container) { // Clear existing content except slot const slot = container.querySelector('slot'); if (slot && slot.parentElement) { const slotContent = this.slottedContent; slot.outerHTML = slotContent; } } } } private applyContentProperty() { if (this.content) { const container = this.querySelector('.usa-prose'); if (container) { // Apply content property to dedicated div let contentDiv = container.querySelector('.prose-content'); if (!contentDiv) { contentDiv = document.createElement('div'); contentDiv.className = 'prose-content'; container.appendChild(contentDiv); } contentDiv.innerHTML = this.content; } } } private getProseClasses(): string { const classes = ['usa-prose']; if (this.variant !== 'default') { classes.push(`usa-prose--${this.variant}`); } if (this.width !== 'default') { classes.push(`usa-prose--${this.width}`); } return classes.join(' '); } private handleContentChange() { this.dispatchEvent( new CustomEvent('prose-change', { detail: { content: this.content, variant: this.variant, } as ProseDetail, bubbles: true, composed: true, }) ); } override disconnectedCallback() { super.disconnectedCallback(); // Clean up USWDS behavior try { if (typeof window !== 'undefined' && typeof (window as any).USWDS !== 'undefined') { // USWDS available but no setup needed } } catch (error) { console.warn('📋 Prose: Cleanup failed:', error); } // Additional cleanup for event listeners would go here } override render() { return html`
`; } // Public API methods setVariant(variant: 'default' | 'condensed' | 'expanded') { this.variant = variant; this.handleContentChange(); } setWidth(width: 'default' | 'narrow' | 'wide') { this.width = width; this.handleContentChange(); } setContent(content: string) { this.content = content; this.handleContentChange(); } getContent(): string { return this.content; } addContent(content: string) { this.content += content; this.handleContentChange(); } clearContent() { this.content = ''; this.handleContentChange(); } }