import { injectable } from "@joist/di"; import { attr, css, element, html, listen } from "@joist/element"; import { RADIO_CTX, type RadioContainer } from "./context.js"; declare global { interface HTMLElementTagNameMap { "usa-radio": USARadioElement; } } @injectable({ name: "usa-radio-ctx", provideSelfAs: [RADIO_CTX], }) @element({ tagName: "usa-radio", shadowDom: [ css` :host { --usa-input-bg-color: #fff; --usa-input-border-color: #5c5c5c; --usa-input-text-color: #1b1b1b; --usa-input-focus-color: #2491ff; --usa-input-disabled-bg-color: #fff; --usa-input-disabled-border-color: #757575; --usa-input-disabled-text-color: #757575; --usa-input-active-color: #005ea2; --usa-radio-active-color: var(--usa-input-active-color); --usa-radio-tiled-bg: rgba(0, 94, 162, 0.1); --usa-radio-focus-color: var(--usa-input-focus-color); --usa-input-radius: 0; display: flex; flex-direction: column; gap: 1rem; max-width: 30rem; margin-bottom: 1.5rem; } label { display: flex; cursor: pointer; gap: 0.5rem; position: relative; } input { position: absolute; } label::before { content: " "; display: block; left: 0; height: 1.25rem; border-radius: 99rem; width: 1.25rem; background: var(--usa-input-bg-color); box-shadow: 0 0 0 2px var(--usa-input-text-color); flex: 0 0 1.25rem; position: relative; z-index: 1000; } label:has(input:checked)::before { background-color: var(--usa-radio-active-color); box-shadow: 0 0 0 2px var(--usa-radio-active-color), inset 0 0 0 2px var(--usa-input-bg-color); } label:has(input:checked:is(:disabled))::before { background-color: var(--usa-input-disabled-text-color); box-shadow: 0 0 0 2px var(--usa-input-disabled-text-color), inset 0 0 0 2px var(--usa-input-bg-color); } label:has(input:disabled) { cursor: not-allowed; color: var(--usa-input-disabled-text-color) !important; } label:has(input:disabled)::before { background-color: var(--usa-input-disabled-bg-color); box-shadow: 0 0 0 2px var(--usa-input-disabled-border-color); } label:has(input:focus)::before { outline: 0.25rem solid var(--usa-radio-focus-color); outline-offset: 0.25rem; } :host([tiled]) { gap: 0.5rem; } :host([tiled]) label { background-color: var(--usa-input-bg-color); border: 2px solid var(--usa-input-border-color); border-radius: 0.25rem; color: var(--usa-input-text-color); padding: 0.75rem 1rem 0.75rem 0.75rem; } .radio { background: var(--usa-input-bg-color); box-shadow: 0 0 0 2px var(--usa-input-text-color); display: flex; align-items: center; justify-content: center; height: 1.25rem; min-width: 1.25rem; max-width: 1.25rem; border-radius: var(--usa-input-radius); position: relative; margin-right: 0.75rem; } input:disabled + .radio { background-color: var(--usa-input-disabled-bg-color); box-shadow: 0 0 0 2px var(--usa-input-disabled-border-color); } input:disabled:is(:checked) + .radio { background-color: var(--usa-input-disabled-text-color); box-shadow: 0 0 0 2px var(--usa-input-disabled-border-color); } :host([disabled]) label { color: var(--usa-input-disabled-text-color); cursor: not-allowed; } input:checked + .radio { background-color: var(--usa-input-active-color); box-shadow: 0 0 0 2px var(--usa-input-active-color); } input:focus + .radio { outline: 0.25rem solid var(--usa-input-focus-color); outline-offset: 0.25rem; } :host([tiled]) label:has(input:checked) { background-color: rgba(0, 94, 162, 0.1); border-color: var(--usa-input-active-color); } :host([tiled]) label:has(input:checked:is(:disabled)) { background-color: var(--usa-input-disabled-bg-color); border-color: var(--usa-input-disabled-border-color); } slot { display: flex; } `, html` `, ], }) export class USARadioElement extends HTMLElement implements RadioContainer { static formAssociated = true; @attr() accessor value = ""; @attr() accessor name = ""; @attr() accessor required = false; @attr({ observed: false, }) accessor tiled = false; #internals = this.attachInternals(); #firstInput: HTMLInputElement | null = null; addRadioOption(el: HTMLElement) { this.shadowRoot?.append(el); if (this.#firstInput === null) { this.#firstInput = el.querySelector("input"); } this.#syncFormState(); } @listen("change") onChange(e: Event) { if (e.target instanceof HTMLInputElement) { if (e.target.checked) { this.value = e.target.value; this.#syncFormState(); } } } #syncFormState() { this.#internals.setFormValue(this.value); this.#internals.setValidity({}); if (this.#firstInput?.validationMessage) { this.#internals.setValidity( { customError: true }, this.#firstInput.validationMessage, this.#firstInput, ); } } }