/**
* 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);
};