import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
// Import official USWDS compiled CSS
import '../../styles/styles.css';
/**
* USA Summary Box Web Component
*
* A simple, accessible USWDS summary box implementation as a custom element.
* Uses official USWDS classes and styling with minimal custom code.
*
* @element usa-summary-box
*
* @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-summary-box/src/styles/_usa-summary-box.scss
* @uswds-docs https://designsystem.digital.gov/components/summary-box/
* @uswds-guidance https://designsystem.digital.gov/components/summary-box/#guidance
* @uswds-accessibility https://designsystem.digital.gov/components/summary-box/#accessibility
*/
@customElement('usa-summary-box')
export class USASummaryBox 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: String })
heading = '';
@property({ type: String })
content = '';
@property({ type: String, attribute: 'heading-level' })
headingLevel = 'h3';
// Track whether we're using innerHTML mode to avoid Lit marker conflicts
private isUsingInnerHTML = false;
// 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) {
// ARCHITECTURE: Script Tag Pattern
// USWDS is loaded globally via script tag in .storybook/preview-head.html
// Components just render HTML - USWDS enhances automatically via window.USWDS
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 named slots first
const slottedElements = Array.from(this.querySelectorAll('[slot]'));
slottedElements.forEach((element) => {
const slotName = element.getAttribute('slot');
if (slotName) {
const slotElement = this.querySelector(`slot[name="${slotName}"]`);
if (slotElement) {
// Replace the slot element with the actual slotted content
slotElement.replaceWith(element);
}
}
});
// 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-summary-box
const defaultSlottedElements = Array.from(this.children).filter(
(el) =>
!el.hasAttribute('slot') &&
el.tagName !== 'STYLE' &&
!el.classList.contains('usa-summary-box')
);
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 renderHeading() {
if (!this.heading) return '';
switch (this.headingLevel) {
case 'h1':
return html`
${this.heading}
`;
case 'h2':
return html`
${this.heading}
`;
case 'h3':
return html`
${this.heading}
`;
case 'h4':
return html`
${this.heading}
`;
case 'h5':
return html`
${this.heading}
`;
case 'h6':
return html`
${this.heading}
`;
default:
return html`
${this.heading}
`;
}
}
override updated(changedProperties: Map) {
super.updated(changedProperties);
// Update content via innerHTML when content property changes
// CRITICAL: Cannot use unsafeHTML directive in Light DOM components
// Must use innerHTML imperatively instead
if (changedProperties.has('content')) {
const textElement = this.querySelector('.usa-summary-box__text');
if (textElement) {
if (this.content) {
// Switching to property content - use innerHTML
textElement.innerHTML = this.content;
this.isUsingInnerHTML = true;
} else if (this.isUsingInnerHTML) {
// Switching back to slot content - clear innerHTML and trigger re-render
// This ensures Lit's slot markers are restored properly
textElement.innerHTML = '';
this.isUsingInnerHTML = false;
this.requestUpdate();
}
}
}
}
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['summary-box'] && typeof USWDS['summary-box'].off === 'function') {
USWDS['summary-box'].off(this);
}
}
} catch (error) {
console.warn('📋 SummaryBox: Cleanup failed:', error);
}
// Additional cleanup for event listeners would go here
}
override render() {
return html`