/** * You can create radio buttons by using the {@link UwcInputElement.type | `[type=radio]`} attribute. * * Doing so will act pretty much the same way as native inputs, so it might be helpful to consult * [the MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/radio). * * @example * * ```html * * ``` * * @module */ import { create, createEventRegistrar, dispatch, } from "@hesxenon/prelude/Dom.js"; import { Events } from "../events"; import { Connect } from "../uwc-input"; import * as Root from "../parts/root"; import { backup } from "../../utils/ElementBackup"; const getRadiogroup = (input: UwcInputElement) => input.form ?? document.body; const getAll = (input: UwcInputElement) => !input.name ? [] : Array.from( getRadiogroup(input).querySelectorAll( `uwc-input[type=radio][name="${input.name}"]`, ), ); const getOthers = (input: UwcInputElement) => getAll(input).filter((other) => other !== input); const getRelative = (input: UwcInputElement, offset: number) => { const all = getAll(input); return all.at(Math.max(0, all.indexOf(input) + offset)); }; const selectNext = (input: UwcInputElement) => getRelative(input, +1)?.click(); const selectPrevious = (input: UwcInputElement) => getRelative(input, -1)?.click(); const initializeInput: Connect = (input, { disconnect }) => { backup(input, disconnect); input.checked = input.checked ?? false; input.role = "radio"; input.tabIndex = input.tabIndex === -1 ? 0 : input.tabIndex; }; /** * @internal */ export const connectRadio: Connect = (input, opts) => { const initial: Partial = { checked: input.checked, value: input.value, }; let cache = undefined as undefined | boolean; const syncFormValues = () => { dispatch(input, { type: Events.formValuesChanged, detail: !input.checked ? [] : [String(input.value ?? "on")], }); }; const validate = () => { dispatch(input, { type: Events.validityStateChanged, detail: input.disabled || input.readonly ? {} : { valueMissing: input.required && !input.checked, }, }); }; const handleUserSelected = (e: Event) => { if ( e.defaultPrevented || input.readonly || input.disabled || input.checked ) { return; } input.focus(); input.checked = true; dispatch(input, { type: "input" }); dispatch(input, { type: "change" }); }; const checkmark = create("div", { part: "checkmark" }); const on = createEventRegistrar(input, opts.disconnect); on("click", handleUserSelected); on([Events.checkedChanged, Events.valueChanged], () => { if (cache !== input.checked) { cache = input.checked; input.setAttribute("aria-checked", String(cache)); if (cache) { getOthers(input).forEach((input) => (input.checked = false)); } syncFormValues(); } }); on([Events.nameChanged, Events.formAssociated], syncFormValues); on(Events.clear, () => { input.checked = false; }); on([Events.formValuesChanged, Events.requiredChanged], validate); on("keydown", (e) => { if (e.key === " ") { handleUserSelected(e); } if (e.key === "ArrowRight" || e.key === "ArrowDown") { selectNext(input); } if (e.key === "ArrowLeft" || e.key === "ArrowUp") { selectPrevious(input); } }); on(Events.formReset, () => { Object.assign(input, initial); }); Root.replaceChildren(input, [checkmark]); initializeInput(input, opts); };