/** * @license * Copyright 2023 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {Validator} from './validator.js'; /** * Constraint validation properties for a radio. */ export interface RadioState { /** * Whether the radio is checked. */ readonly checked: boolean; /** * Whether the radio is required. */ readonly required: boolean; } /** * Radio constraint validation properties for a single radio and its siblings. */ export type RadioGroupState = readonly [RadioState, ...RadioState[]]; /** * A validator that provides constraint validation that emulates * `` validation. */ export class RadioValidator extends Validator { private radioElement?: HTMLInputElement; protected override computeValidity(states: RadioGroupState) { if (!this.radioElement) { // Lazily create the radio element this.radioElement = document.createElement('input'); this.radioElement.type = 'radio'; // A name is required for validation to run this.radioElement.name = 'group'; } let isRequired = false; let isChecked = false; for (const {checked, required} of states) { if (required) { isRequired = true; } if (checked) { isChecked = true; } } // Firefox v119 doesn't compute grouped radio validation correctly while // they are detached from the DOM, which is why we don't render multiple // virtual s. Instead, we can check the required/checked states and // grab the i18n'd validation message if the value is missing. this.radioElement.checked = isChecked; this.radioElement.required = isRequired; return { validity: { valueMissing: isRequired && !isChecked, }, validationMessage: this.radioElement.validationMessage, }; } protected override equals( prevGroup: RadioGroupState, nextGroup: RadioGroupState, ) { if (prevGroup.length !== nextGroup.length) { return false; } for (let i = 0; i < prevGroup.length; i++) { const prev = prevGroup[i]; const next = nextGroup[i]; if (prev.checked !== next.checked || prev.required !== next.required) { return false; } } return true; } protected override copy(states: RadioGroupState): RadioGroupState { // Cast as unknown since typescript does not have enough information to // infer that the array always has at least one element. return states.map(({checked, required}) => ({ checked, required, })) as unknown as RadioGroupState; } }