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.flagImageAlt}

${this.headerText}

Dot gov

Official websites use .gov
A .gov website belongs to an official government organization in the United States.

Https

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.

`; } }