import { html, css, PropertyValues } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { USWDSBaseComponent } from '../../utils/base-component.js'; import { initializeSearch } from './usa-search-behavior.js'; // Import official USWDS compiled CSS import '../../styles/styles.css'; /** * USA Search Web Component * * Minimal wrapper around USWDS search functionality. * Uses USWDS-mirrored behavior pattern for 100% behavioral parity. * * @element usa-search * @fires search-submit - Dispatched when search form is submitted * * @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-search/src/index.js * @uswds-css-reference https://github.com/uswds/uswds/tree/develop/packages/usa-search/src/styles/_usa-search.scss * @uswds-docs https://designsystem.digital.gov/components/search/ * @uswds-guidance https://designsystem.digital.gov/components/search/#guidance * @uswds-accessibility https://designsystem.digital.gov/components/search/#accessibility */ @customElement('usa-search') export class USASearch extends USWDSBaseComponent { static override styles = css` :host { display: inline-block; width: 100%; } `; @property({ type: String }) placeholder = 'Search'; @property({ type: String }) label = ''; @property({ type: String }) buttonText = 'Search'; @property({ type: String }) value = ''; @property({ type: String }) size: 'small' | 'medium' | 'big' = 'medium'; @property({ type: Boolean, reflect: true }) disabled = false; @property({ type: String }) name = 'search'; @property({ type: String }) inputId = 'search-field'; @property({ type: String, attribute: 'button-id' }) buttonId = 'search-button'; @property({ type: String, attribute: 'submit-icon-src' }) submitIconSrc = '/img/search.svg'; @property({ type: String, attribute: 'submit-icon-alt' }) submitIconAlt = ''; @property({ type: Boolean }) toggleable = false; // Store cleanup function from behavior private cleanup?: () => void; override connectedCallback() { super.connectedCallback(); this.setAttribute('data-web-component-managed', 'true'); } override async firstUpdated(changedProperties: PropertyValues) { // 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-search-behavior.ts) that replicates USWDS source exactly super.firstUpdated(changedProperties as any); // Listen for form submission to dispatch custom event this.setupFormListener(); // Wait for DOM to be fully rendered await this.updateComplete; await new Promise((resolve) => requestAnimationFrame(() => resolve(undefined))); // Initialize using mirrored USWDS behavior this.cleanup = initializeSearch(this); } override disconnectedCallback() { super.disconnectedCallback(); this.cleanup?.(); } private setupFormListener() { // Only listen for form submission to dispatch custom event for web component consumers const form = this.querySelector('form'); if (form) { form.addEventListener('submit', (e) => { const input = form.querySelector('.usa-search__input') as HTMLInputElement; // For toggleable variant: don't prevent submission, let USWDS behavior handle it // For regular variant: prevent and dispatch custom event if (!this.toggleable) { e.preventDefault(); } // Don't dispatch events if disabled if (this.disabled) { return; } // Dispatch custom event for web component consumers this.dispatchEvent( new CustomEvent('search-submit', { detail: { query: input?.value || '', form: form, }, bubbles: true, composed: true, }) ); }); // Add keydown listener for Enter key handling const input = form.querySelector('.usa-search__input') as HTMLInputElement; if (input) { input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !this.disabled) { // Dispatch the same search-submit event this.dispatchEvent( new CustomEvent('search-submit', { detail: { query: input.value || '', form: form, }, bubbles: true, composed: true, }) ); } }); } } } private handleInputChange(e: Event) { const input = e.target as HTMLInputElement; this.value = input.value; // Dispatch search-input event this.dispatchEvent( new CustomEvent('search-input', { detail: { query: input.value, input: input, }, bubbles: true, composed: true, }) ); } override render() { const sizeClass = this.size ? `usa-search--${this.size}` : ''; const labelText = this.label || this.placeholder || 'Search'; // Render toggleable big search variant (used in headers) // Wrap in header context per USWDS JavaScript requirements if (this.toggleable) { return html`
`; } // Render regular always-visible search return html` `; } }