import { html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { USWDSBaseComponent } from '../../utils/base-component.js';
import { initializeBanner } from './usa-banner-behavior.js';
// Import official USWDS compiled CSS
import '../../styles/styles.css';
/**
* USA Banner Web Component
*
* Minimal wrapper around USWDS banner functionality.
* Uses USWDS-mirrored behavior pattern for 100% behavioral parity.
*
* @element usa-banner
* @fires banner-toggle - Dispatched when banner is toggled
*
* @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-js-reference https://github.com/uswds/uswds/tree/develop/packages/usa-banner/src/index.js
* @uswds-css-reference https://github.com/uswds/uswds/tree/develop/packages/usa-banner/src/styles/_usa-banner.scss
* @uswds-docs https://designsystem.digital.gov/components/banner/
* @uswds-guidance https://designsystem.digital.gov/components/banner/#guidance
* @uswds-accessibility https://designsystem.digital.gov/components/banner/#accessibility
*/
@customElement('usa-banner')
export class USABanner extends USWDSBaseComponent {
static override styles = css`
:host {
display: block;
}
`;
@property({ type: String })
flagImageSrc = '/img/us_flag_small.png';
@property({ type: String })
flagImageAlt = 'U.S. flag';
@property({ type: String })
dotGovIconSrc = '/img/icon-dot-gov.svg';
@property({ type: String })
httpsIconSrc = '/img/icon-https.svg';
@property({ type: String })
headerText = 'An official website of the United States government';
@property({ type: String })
actionText = "Here's how you know";
@property({ type: Boolean, reflect: true })
expanded = false;
// Store cleanup function from behavior
private cleanup?: () => void;
private mutationObserver?: MutationObserver;
private isUpdatingFromMutation = false;
override connectedCallback() {
super.connectedCallback();
this.setAttribute('data-web-component-managed', 'true');
}
override async 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
// ARCHITECTURE: USWDS-Mirrored Behavior Pattern
// Uses dedicated behavior file (usa-banner-behavior.ts) that replicates USWDS source exactly
super.firstUpdated(changedProperties);
// Wait for DOM to be fully rendered
await this.updateComplete;
await new Promise((resolve) => requestAnimationFrame(() => resolve(undefined)));
// CRITICAL: Set hidden attribute on content BEFORE initializing behavior
// This ensures content starts hidden, then behavior manages it based on expanded state
const content = this.querySelector('.usa-banner__content');
if (content && !this.expanded) {
content.setAttribute('hidden', '');
}
// Initialize using mirrored USWDS behavior
this.cleanup = initializeBanner(this);
// Watch for aria-expanded changes from behavior and sync component property
const button = this.querySelector('.usa-banner__button');
if (button) {
this.mutationObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'aria-expanded') {
const newExpanded = button.getAttribute('aria-expanded') === 'true';
if (this.expanded !== newExpanded && !this.isUpdatingFromMutation) {
this.isUpdatingFromMutation = true;
this.expanded = newExpanded;
this.dispatchEvent(
new CustomEvent('banner-toggle', {
detail: { expanded: newExpanded },
bubbles: true,
composed: true,
})
);
this.isUpdatingFromMutation = false;
}
}
});
});
this.mutationObserver.observe(button, {
attributes: true,
attributeFilter: ['aria-expanded'],
});
}
}
override disconnectedCallback() {
super.disconnectedCallback();
this.cleanup?.();
this.mutationObserver?.disconnect();
}
override updated(changedProperties: Map) {
super.updated(changedProperties);
// Handle programmatic expanded property changes (but not from mutation observer)
if (changedProperties.has('expanded') && !this.isUpdatingFromMutation) {
const button = this.querySelector('.usa-banner__button') as HTMLElement;
const content = this.querySelector('.usa-banner__content') as HTMLElement;
const header = this.querySelector('.usa-banner__header') as HTMLElement;
if (button && content && header) {
// Sync ARIA expanded attribute
button.setAttribute('aria-expanded', String(this.expanded));
// Sync hidden attribute
if (this.expanded) {
content.removeAttribute('hidden');
header.classList.add('usa-banner__header--expanded');
} else {
content.setAttribute('hidden', '');
header.classList.remove('usa-banner__header--expanded');
}
}
}
}
override render() {
return html`
${this.headerText}
${this.actionText}
Official websites use .gov
A .gov website belongs to an official government organization
in the United States.
Secure .gov websites use HTTPS
A lock (
) or https:// means you've safely connected to the .gov
website. Share sensitive information only on official, secure websites.