import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
// Removed unsafeHTML import - using safer HTML content handling
// Import official USWDS compiled CSS
import '../../styles/styles.css';
export interface ProcessItem {
heading: string;
content: string;
}
/**
* USA Process List Web Component
*
* A simple, accessible USWDS process list implementation as a custom element.
* Uses official USWDS classes and styling with minimal custom code.
*
* @element usa-process-list
*
* @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-process-list/src/styles/_usa-process-list.scss
* @uswds-docs https://designsystem.digital.gov/components/process-list/
* @uswds-guidance https://designsystem.digital.gov/components/process-list/#guidance
* @uswds-accessibility https://designsystem.digital.gov/components/process-list/#accessibility
*/
@customElement('usa-process-list')
export class USAProcessList extends LitElement {
static override styles = css`
:host {
display: block;
}
/* Hide slotted elements that appear as direct children (light DOM slot workaround) */
:host > [slot] {
display: none !important;
}
`;
@property({ type: Array })
items: ProcessItem[] = [];
@property({ type: String })
headingLevel = 'h4';
// 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');
}
override firstUpdated(changedProperties: Map) {
super.firstUpdated(changedProperties);
// Move slotted content into their slot placeholders (light DOM slot workaround)
this.moveSlottedContent();
}
private moveSlottedContent() {
// In light DOM, slots don't automatically project content
// We need to manually move slotted elements into their slot locations
// Handle default slot (elements without slot attribute)
const defaultSlot = this.querySelector('slot:not([name])');
if (defaultSlot) {
// Get all direct children that should go in the default slot
// Exclude: elements with slot attribute, STYLE tags, and elements already inside .usa-process-list
const defaultSlottedElements = Array.from(this.children).filter(
(el) =>
!el.hasAttribute('slot') &&
el.tagName !== 'STYLE' &&
!el.classList.contains('usa-process-list')
);
if (defaultSlottedElements.length > 0) {
// Create a document fragment to hold the slotted content
const fragment = document.createDocumentFragment();
defaultSlottedElements.forEach((el) => {
fragment.appendChild(el);
});
// Replace the slot with the fragment
defaultSlot.replaceWith(fragment);
} else {
// No default slot content, just remove the empty slot
defaultSlot.remove();
}
}
}
private renderItemHeading(heading: string) {
switch (this.headingLevel) {
case 'h1':
return html`${heading}
`;
case 'h2':
return html`${heading}
`;
case 'h3':
return html`${heading}
`;
case 'h4':
return html`${heading}
`;
case 'h5':
return html`${heading}
`;
case 'h6':
return html`${heading}
`;
default:
return html`${heading}
`;
}
}
private renderProcessItem(item: ProcessItem): any {
return html`
${this.renderItemHeading(item.heading)}
`;
}
override disconnectedCallback() {
super.disconnectedCallback();
// Clean up USWDS behavior
try {
if (typeof window !== 'undefined' && typeof (window as any).USWDS !== 'undefined') {
const USWDS = (window as any).USWDS;
if (USWDS['process-list'] && typeof USWDS['process-list'].off === 'function') {
USWDS['process-list'].off(this);
}
}
} catch (error) {
console.warn('📋 ProcessList: Cleanup failed:', error);
}
// Additional cleanup for event listeners would go here
}
override render() {
// Always render the container to avoid hierarchy errors with light DOM slots
return html`
${this.items.length === 0 ? html`` : this.items.map((item) => this.renderProcessItem(item))}
`;
}
}