web-ui-pack
Version:
Web package with UI elements
279 lines (278 loc) • 9.2 kB
JavaScript
import { WUPcssHidden } from "../styles";
import WUPBaseControl from "./baseControl";
const tagName = "wup-radio";
export default class WUPRadioControl extends WUPBaseControl {
#ctr = this.constructor;
static $ariaReadonly = __wupln("readonly", "aria");
static get $styleRoot() {
return `:root {
--ctrl-radio-item-r: 14px;
--ctrl-radio-item-r-on: 8px;
--ctrl-radio-item-bg: none;
--ctrl-radio-item-on: var(--ctrl-focus);
--ctrl-radio-item-border: #0003;
--ctrl-radio-item-border-w: 2px;
--ctrl-radio-gap: 7px;
}
[wupdark] {
--ctrl-radio-item-border: var(--ctrl-label);
}`;
}
static get $style() {
return `${super.$style}
:host {
position: relative;
padding: var(--ctrl-padding);
}
:host fieldset {
border: none;
padding: 0;
margin: calc(var(--ctrl-radio-gap) * -0.5) calc(var(--ctrl-radio-gap) * -1);
display: flex;
flex-wrap: wrap;
}
:host legend {
display: block;
position: absolute;
top: 0.2em;
transform-origin: top left;
transform: scale(0.9);
margin: 0 var(--ctrl-radio-gap);
padding: 0;
box-sizing: border-box;
max-width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
font-weight: normal;
text-decoration: none;
}
:host strong {
font: inherit;
}
:host label {
padding: var(--ctrl-radio-gap);
gap: var(--ctrl-radio-gap);
}
:host[w-reverse] label {
flex-direction: row-reverse;
}
:host input {${WUPcssHidden}}
:host[readonly],
:host[readonly] legend,
:host[readonly] label {
cursor: default;
}
:host [icon] {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--ctrl-radio-item-r);
height: var(--ctrl-radio-item-r);
box-sizing: border-box;
background: var(--ctrl-radio-item-bg);
box-shadow: 0 0 1px var(--ctrl-radio-item-border-w) var(--ctrl-radio-item-border);
border-radius: 50%;
}
:host [icon]:after {
content: "";
display: inline-block;
width: var(--ctrl-radio-item-r-on);
height: var(--ctrl-radio-item-r-on);
border-radius: 50%;
transition: background-color var(--anim);
}
:host input:checked + [icon]:after {
background: var(--ctrl-radio-item-on);
}
not all and (prefers-reduced-motion) {
:host label {
transition: color var(--anim);
}
:host [icon] {
transition: background-color var(--anim);
}
}
:host label:focus-within {
color: var(--ctrl-selected);
}
:host label:focus-within [icon] {
--ctrl-radio-item-border: var(--ctrl-selected);
}
(hover: hover) and (pointer: fine) {
:host label:hover {
color: var(--ctrl-selected);
}
:host label:hover [icon] {
--ctrl-radio-item-border: var(--ctrl-selected);
}
}`;
}
static $defaults = {
...WUPBaseControl.$defaults,
validationRules: { ...WUPBaseControl.$defaults.validationRules },
items: [],
reverse: false,
};
static cloneDefaults() {
const d = super.cloneDefaults();
d.items = [];
return d;
}
parse(attrValue) {
const a = this.getItems();
if (!a?.length) {
return undefined;
}
const r = attrValue === "" ? a.find((o) => o.value == null) : a.find((o) => o.value && `${o.value}` === attrValue);
return r?.value;
}
gotReady() {
super.gotReady();
this.appendEvent(this, "input", this.gotInput);
}
gotInput(e) {
this.setValue(this.getItems()[e.target._index].value, 3);
}
#cachedItems;
getItems() {
if (!this.#cachedItems) {
const { items } = this._opts;
this.#cachedItems = typeof items === "function" ? items() : items;
}
return this.#cachedItems;
}
$refFieldset = document.createElement("fieldset");
$refItems = [];
renderControl() {
this.$refFieldset.appendChild(document.createElement("legend")).appendChild(this.$refTitle);
this.appendChild(this.$refFieldset);
}
renderItems(parent) {
this.$refItems.forEach((r) => r.parentElement.remove());
this.$refItems.length = 0;
const arr = this.getItems();
if (!arr?.length) {
return;
}
const nm = this.#ctr.$uniqueId + (Date.now() % 1000);
arr.forEach((item, i) => {
const lbl = document.createElement("label");
const s = item.text;
if (typeof s === "function") {
s(item.value, lbl, i, this);
}
else {
lbl.appendChild(document.createTextNode(item.text));
}
const inp = lbl.appendChild(document.createElement("input"));
lbl.appendChild(document.createElement("span")).setAttribute("icon", "");
this.$refItems.push(inp);
inp.id = this.#ctr.$uniqueId;
inp._index = i;
lbl.setAttribute("for", inp.id);
inp.type = "radio";
inp.name = nm;
parent.appendChild(lbl);
if (item.onClick) {
lbl.onclick = (e) => item.onClick?.call(lbl, e, item);
}
});
this.$refItems[0].tabIndex = 0;
setTimeout(() => this.checkInput(this.$value));
}
checkInput(v) {
this.$refInput.checked = false;
this.$refLabel.removeAttribute("checked");
if (v === undefined) {
this.$refInput = this.$refItems[0] || this.$refInput;
}
else {
const item = this.$refItems[this.getItems().findIndex((a) => this.#ctr.$isEqual(a.value, v, this))];
if (!item) {
console.error(`${this.tagName}${this._opts.name ? `[${this._opts.name}]` : ""}. Not found in items`, {
items: this._opts.items,
value: v,
});
}
else {
item.checked = true;
this.$refInput = item;
}
}
this.setupInput();
this.$refLabel = this.$refInput.parentElement;
this.$refInput.checked && this.$refLabel.setAttribute("checked", "");
}
setValue(v, reason) {
if (this.$isReady) {
reason === 1 ? setTimeout(() => this.checkInput(v)) : this.checkInput(v);
}
return super.setValue(v, reason);
}
gotChanges(propsChanged) {
this._opts.items ??= [];
const isNeedRenderItems = !propsChanged || propsChanged.includes("items");
if (isNeedRenderItems) {
this.#cachedItems = undefined;
this.renderItems(this.$refFieldset);
}
super.gotChanges(propsChanged);
this.setAttr("w-reverse", this._opts.reverse, true);
const req = this.validations?.required;
this.setAttr.call(this.$refFieldset, "aria-required", !!req);
}
gotFormChanges(propsChanged) {
super.gotFormChanges(propsChanged);
this.$refInput.disabled = false;
this.$refFieldset.disabled = this.$isDisabled;
this.$ariaDetails(this.$isReadOnly ? this.#ctr.$ariaReadonly : null);
}
setupInputReadonly() {
const r = this.$isReadOnly;
this.$refItems.forEach((a) => (a.readOnly = r));
this.$refFieldset.onclick = r ? (e) => e.preventDefault() : null;
}
gotFocus(e) {
if (e.target !== this.$refInput) {
this.$refInput.focus();
}
return super.gotFocus(e);
}
focus() {
this.$refInput.focus();
return document.activeElement === this.$refInput || super.focus();
}
goShowError(err, target) {
super.goShowError(err, this.$refFieldset);
}
valueToStrCompare(a) {
const at = typeof a.text === "function" ? a.value?.toString() : a.text;
return at?.replace(/\s/g, "") ?? null;
}
valueFromStorage(str) {
if (str === "null") {
return null;
}
const s = str.toLowerCase();
const items = this.getItems();
const item = items.find((a) => this.valueToStrCompare(a)?.toLowerCase() === s);
if (item === undefined) {
this.throwError("Not found in items (search by item.value.toString() & item.text)", {
items,
searchText: str,
});
return undefined;
}
return item.value;
}
valueToStorage(v) {
if (v == null) {
return "null";
}
const items = this.getItems();
const item = items.find((o) => this.#ctr.$isEqual(o.value, v, this)) || { value: v, text: v?.toString() };
return this.valueToStrCompare(item);
}
}
customElements.define(tagName, WUPRadioControl);