@umbraco-ui/uui-radio
Version:
Radio input, Umbraco backoffice style. Package contains two custom elements, <uui-radio> and <uui-radio-group>. You must wrap radio elements in the group, to make the input work. Can participate in native form element.
585 lines (559 loc) • 20 kB
JavaScript
import { UUIHorizontalShakeKeyframes, UUIHorizontalShakeAnimationValue } from '@umbraco-ui/uui-base/lib/animations';
import { defineElement } from '@umbraco-ui/uui-base/lib/registration';
import { css, LitElement, html } from 'lit';
import { query, property } from 'lit/decorators.js';
import { UUIEvent } from '@umbraco-ui/uui-base/lib/events';
import { UUIFormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
class UUIRadioEvent extends UUIEvent {
static {
this.CHANGE = "change";
}
constructor(evName, eventInit = {}) {
super(evName, {
...{ bubbles: true },
...eventInit
});
}
}
var __defProp$1 = Object.defineProperty;
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
var __typeError$1 = (msg) => {
throw TypeError(msg);
};
var __decorateClass$1 = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp$1(target, key, result);
return result;
};
var __accessCheck$1 = (obj, member, msg) => member.has(obj) || __typeError$1("Cannot " + msg);
var __privateAdd$1 = (obj, member, value) => member.has(obj) ? __typeError$1("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateMethod$1 = (obj, member, method) => (__accessCheck$1(obj, member, "access private method"), method);
var _UUIRadioElement_instances, onKeyDown_fn, onChange_fn;
let UUIRadioElement = class extends LitElement {
constructor() {
super();
__privateAdd$1(this, _UUIRadioElement_instances);
this.name = "";
this.value = "";
this.label = "";
this.checked = false;
this.disabled = false;
this.readonly = false;
this.addEventListener("keydown", __privateMethod$1(this, _UUIRadioElement_instances, onKeyDown_fn));
}
focus() {
this._inputElement.focus();
}
click() {
this._inputElement.click();
}
/**
* Call to uncheck the element
* @method uncheck
*/
uncheck() {
this.checked = false;
}
/**
* Call to check the element.
* @method uncheck
*/
check() {
this.checked = true;
}
/**
* Call to make the element focusable, this sets tabindex to 0.
* @method makeFocusable
*/
makeFocusable() {
if (!this.disabled) {
this.removeAttribute("tabindex");
}
}
/**
* Call to make the element focusable, this sets tabindex to -1.
* @method makeUnfocusable
*/
makeUnfocusable() {
if (!this.disabled) {
this.setAttribute("tabindex", "-1");
}
}
render() {
return html` <label>
<input
id="input"
type="radio"
name=${this.name}
value=${this.value}
.checked=${this.checked}
.disabled=${this.disabled || this.readonly}
@change=${__privateMethod$1(this, _UUIRadioElement_instances, onChange_fn)} />
<div id="button"></div>
<div id="label">
${this.label ? html`<span>${this.label}</span>` : html`<slot></slot>`}
</div>
</label>`;
}
};
_UUIRadioElement_instances = new WeakSet();
onKeyDown_fn = function(e) {
if (e.key === " ") {
e.preventDefault();
if (!this.disabled && !this.readonly) {
this._inputElement?.click();
}
}
};
onChange_fn = function(e) {
e.stopPropagation();
const checked = this._inputElement.checked;
this.checked = checked;
if (checked) {
this.focus();
}
this.dispatchEvent(new UUIRadioEvent(UUIRadioEvent.CHANGE));
};
UUIRadioElement.styles = [
UUIHorizontalShakeKeyframes,
css`
:host {
display: block;
box-sizing: border-box;
font-family: inherit;
color: currentColor;
--uui-radio-button-size: var(--uui-size-6,18px);
margin: var(--uui-size-2,6px) 0;
}
label {
position: relative;
box-sizing: border-box;
user-select: none;
display: flex;
align-items: center;
cursor: pointer;
line-height: 18px;
}
:host([readonly]) label {
cursor: default;
}
#input {
width: 0;
height: 0;
opacity: 0;
margin: 0;
}
.label {
margin-top: 2px;
}
#button {
box-sizing: border-box;
display: inline-block;
width: var(--uui-radio-button-size, 18px);
height: var(--uui-radio-button-size, 18px);
background-color: var(--uui-color-surface,#fff);
border: 1px solid var(--uui-color-border-standalone,#c2c2c2);
border-radius: 100%;
margin-right: calc(var(--uui-size-2,6px) * 2);
position: relative;
flex: 0 0 var(--uui-radio-button-size);
}
#button::after {
content: '';
width: calc(var(--uui-radio-button-size) / 2);
height: calc(var(--uui-radio-button-size) / 2);
background-color: var(--uui-color-selected,#3544b1);
border-radius: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
transition: all 0.15s ease-in-out;
}
:host(:hover) #button {
border: 1px solid var(--uui-color-border-emphasis,#a1a1a1);
}
:host(:focus) {
outline: none;
}
:host(:focus-within) input:focus-visible + #button {
outline: 2px solid var(--uui-color-focus,#3879ff);
}
input:checked ~ #button::after {
transform: translate(-50%, -50%) scale(1);
}
input:checked ~ #button {
border: 1px solid var(--uui-color-selected,#3544b1);
}
input:checked:hover ~ #button {
border: 1px solid var(--uui-color-selected-emphasis,rgb(
70,
86,
200
));
}
input:checked:hover ~ #button::after {
background-color: var(--uui-color-selected-emphasis,rgb(
70,
86,
200
));
}
:host([disabled]) label {
cursor: not-allowed;
opacity: 0.5;
}
:host([disabled]) .label {
color: var(--uui-color-disabled-contrast,#c4c4c4);
}
:host([disabled]) input ~ #button {
border: 1px solid var(--uui-color-disabled-contrast,#c4c4c4);
}
:host([disabled]) input:checked ~ #button {
border: 1px solid var(--uui-color-disabled-contrast,#c4c4c4);
}
:host([disabled]) input:checked ~ #button::after {
background-color: var(--uui-color-disabled-contrast,#c4c4c4);
}
:host([disabled]:active) #button {
animation: ${UUIHorizontalShakeAnimationValue};
}
@media (prefers-reduced-motion) {
:host([disabled]:active) #button {
animation: none;
}
#button::after {
transition: none;
}
}
`
];
__decorateClass$1([
query("#input")
], UUIRadioElement.prototype, "_inputElement", 2);
__decorateClass$1([
property({ type: String })
], UUIRadioElement.prototype, "name", 2);
__decorateClass$1([
property({ type: String })
], UUIRadioElement.prototype, "value", 2);
__decorateClass$1([
property({ type: String })
], UUIRadioElement.prototype, "label", 2);
__decorateClass$1([
property({ type: Boolean, reflect: true })
], UUIRadioElement.prototype, "checked", 2);
__decorateClass$1([
property({ type: Boolean, reflect: true })
], UUIRadioElement.prototype, "disabled", 2);
__decorateClass$1([
property({ type: Boolean, reflect: true })
], UUIRadioElement.prototype, "readonly", 2);
UUIRadioElement = __decorateClass$1([
defineElement("uui-radio")
], UUIRadioElement);
class UUIRadioGroupEvent extends UUIEvent {
static {
this.CHANGE = "change";
}
constructor(evName, eventInit = {}) {
super(evName, {
...{ bubbles: true },
...eventInit
});
}
}
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _selected, _radioElements, _onFocusIn, _onFocusOut, _onChildBlur, _onSelectClick, _onKeydown, _UUIRadioGroupElement_instances, onSlotChange_fn, setNameOnRadios_fn, updateRadioElementsCheckedState_fn, setDisableOnRadios_fn, setReadonlyOnRadios_fn, findAdjacentRadioElement_fn, selectPreviousRadio_fn, selectNextRadio_fn, fireChangeEvent_fn;
const ARROW_LEFT = "ArrowLeft";
const ARROW_UP = "ArrowUp";
const ARROW_RIGHT = "ArrowRight";
const ARROW_DOWN = "ArrowDown";
const SPACE = " ";
const ENTER = "Enter";
let UUIRadioGroupElement = class extends UUIFormControlMixin(LitElement, "") {
constructor() {
super();
__privateAdd(this, _UUIRadioGroupElement_instances);
this.disabled = false;
this.readonly = false;
__privateAdd(this, _selected, null);
__privateAdd(this, _radioElements, []);
__privateAdd(this, _onFocusIn, (event) => {
__privateGet(this, _radioElements)?.forEach((el) => {
if (el !== event.target) {
el.makeUnfocusable();
} else {
el.makeFocusable();
}
});
});
__privateAdd(this, _onFocusOut, (event) => {
if (this.contains(event.relatedTarget)) return;
if (__privateGet(this, _selected) !== null) return;
__privateGet(this, _radioElements)?.forEach((el) => {
el.makeFocusable();
});
});
__privateAdd(this, _onChildBlur, () => {
this.pristine = false;
});
__privateAdd(this, _onSelectClick, (e) => {
if (e.target.checked === true) {
this.value = e.target.value;
__privateMethod(this, _UUIRadioGroupElement_instances, fireChangeEvent_fn).call(this);
}
});
__privateAdd(this, _onKeydown, (e) => {
switch (e.key) {
case ARROW_LEFT:
case ARROW_UP: {
e.preventDefault();
__privateMethod(this, _UUIRadioGroupElement_instances, selectPreviousRadio_fn).call(this);
break;
}
case ARROW_RIGHT:
case ARROW_DOWN: {
e.preventDefault();
__privateMethod(this, _UUIRadioGroupElement_instances, selectNextRadio_fn).call(this);
break;
}
case SPACE: {
if (__privateGet(this, _selected) === null) {
this.value = __privateMethod(this, _UUIRadioGroupElement_instances, findAdjacentRadioElement_fn).call(this, 1, false)?.value;
__privateMethod(this, _UUIRadioGroupElement_instances, fireChangeEvent_fn).call(this);
}
break;
}
case ENTER:
this.submit();
}
});
this.addEventListener("keydown", __privateGet(this, _onKeydown));
this.addEventListener("focusin", __privateGet(this, _onFocusIn));
this.addEventListener("focusout", __privateGet(this, _onFocusOut));
this.updateComplete.then(() => {
__privateMethod(this, _UUIRadioGroupElement_instances, updateRadioElementsCheckedState_fn).call(this, this.value);
});
}
get value() {
return super.value;
}
set value(newValue) {
super.value = newValue;
__privateMethod(this, _UUIRadioGroupElement_instances, updateRadioElementsCheckedState_fn).call(this, newValue);
}
connectedCallback() {
super.connectedCallback();
if (!this.hasAttribute("role")) this.setAttribute("role", "radiogroup");
}
updated(_changedProperties) {
super.updated(_changedProperties);
if (_changedProperties.has("disabled")) {
__privateMethod(this, _UUIRadioGroupElement_instances, setDisableOnRadios_fn).call(this, this.disabled);
}
if (_changedProperties.has("readonly")) {
__privateMethod(this, _UUIRadioGroupElement_instances, setReadonlyOnRadios_fn).call(this, this.readonly);
}
if (_changedProperties.has("name")) {
__privateMethod(this, _UUIRadioGroupElement_instances, setNameOnRadios_fn).call(this, _changedProperties.get("name"));
}
}
/**
* This method enables <label for="..."> to focus the select
*/
async focus() {
await this.updateComplete;
if (__privateGet(this, _selected) !== null) {
__privateGet(this, _radioElements)[__privateGet(this, _selected)]?.focus();
} else {
__privateMethod(this, _UUIRadioGroupElement_instances, findAdjacentRadioElement_fn).call(this, 1, false)?.focus();
}
}
async blur() {
await this.updateComplete;
if (__privateGet(this, _selected) !== null) {
__privateGet(this, _radioElements)[__privateGet(this, _selected)]?.blur();
} else {
__privateMethod(this, _UUIRadioGroupElement_instances, findAdjacentRadioElement_fn).call(this, 1, false)?.blur();
}
}
async click() {
await this.updateComplete;
if (__privateGet(this, _selected) !== null) {
__privateGet(this, _radioElements)[__privateGet(this, _selected)]?.click();
} else {
__privateMethod(this, _UUIRadioGroupElement_instances, findAdjacentRadioElement_fn).call(this, 1, false)?.click();
}
}
getFormElement() {
if (__privateGet(this, _radioElements) && __privateGet(this, _selected)) {
return __privateGet(this, _radioElements)[__privateGet(this, _selected)];
}
return void 0;
}
render() {
return html` <slot @slotchange=${__privateMethod(this, _UUIRadioGroupElement_instances, onSlotChange_fn)}></slot> `;
}
};
_selected = new WeakMap();
_radioElements = new WeakMap();
_onFocusIn = new WeakMap();
_onFocusOut = new WeakMap();
_onChildBlur = new WeakMap();
_onSelectClick = new WeakMap();
_onKeydown = new WeakMap();
_UUIRadioGroupElement_instances = new WeakSet();
onSlotChange_fn = function(e) {
e.stopPropagation();
__privateGet(this, _radioElements)?.forEach((el) => {
el.removeEventListener(
UUIRadioEvent.CHANGE,
// @ts-ignore TODO: fix typescript error
__privateGet(this, _onSelectClick)
);
el.removeEventListener("blur", __privateGet(this, _onChildBlur));
});
__privateSet(this, _selected, null);
__privateSet(this, _radioElements, e.target.assignedElements({ flatten: true }).filter((el) => el instanceof UUIRadioElement));
if (__privateGet(this, _radioElements).length === 0) return;
__privateGet(this, _radioElements).forEach((el) => {
el.addEventListener(
UUIRadioEvent.CHANGE,
// @ts-ignore TODO: fix typescript error
__privateGet(this, _onSelectClick)
);
el.addEventListener("blur", __privateGet(this, _onChildBlur));
});
__privateMethod(this, _UUIRadioGroupElement_instances, setNameOnRadios_fn).call(this, this.name);
if (this.disabled) {
__privateMethod(this, _UUIRadioGroupElement_instances, setDisableOnRadios_fn).call(this, true);
}
if (this.readonly) {
__privateMethod(this, _UUIRadioGroupElement_instances, setReadonlyOnRadios_fn).call(this, true);
}
const checkedRadios = __privateGet(this, _radioElements).filter((el) => el.checked === true);
if (checkedRadios.length > 1) {
__privateGet(this, _radioElements).forEach((el) => {
el.checked = false;
});
this.value = "";
console.error(
"There can only be one checked radio among the <" + this.nodeName + "> children",
this
);
}
if (checkedRadios.length === 1) {
const firstCheckedRadio = checkedRadios[0];
this.value = firstCheckedRadio.value;
__privateSet(this, _selected, __privateGet(this, _radioElements).indexOf(firstCheckedRadio));
}
};
setNameOnRadios_fn = function(name) {
__privateGet(this, _radioElements)?.forEach((el) => el.name = name);
};
updateRadioElementsCheckedState_fn = function(newValue) {
const notChecked = [];
__privateGet(this, _radioElements).forEach((el, index) => {
if (el.value === newValue) {
el.checked = true;
el.makeFocusable();
__privateSet(this, _selected, index);
} else {
el.checked = false;
notChecked.push(el);
}
});
if (__privateGet(this, _selected) !== null) {
notChecked.forEach((el) => el.makeUnfocusable());
}
};
setDisableOnRadios_fn = function(value) {
__privateGet(this, _radioElements)?.forEach((el) => el.disabled = value);
};
setReadonlyOnRadios_fn = function(value) {
__privateGet(this, _radioElements)?.forEach((el) => el.readonly = value);
};
findAdjacentRadioElement_fn = function(direction = 1, skipFirst = true) {
if (!__privateGet(this, _radioElements) || __privateGet(this, _radioElements).length === 0) return null;
const len = __privateGet(this, _radioElements).length;
let index = __privateGet(this, _selected) ?? 0;
for (let i = 0; i < len + 1; i++) {
if (!skipFirst || i > 0) {
const radioElement = __privateGet(this, _radioElements)[index];
if (!radioElement.disabled && !radioElement.readonly) {
return radioElement;
}
}
index = (index + direction + len) % len;
}
return null;
};
selectPreviousRadio_fn = function() {
this.value = __privateMethod(this, _UUIRadioGroupElement_instances, findAdjacentRadioElement_fn).call(this, -1)?.value ?? "";
__privateGet(this, _radioElements)[__privateGet(this, _selected) ?? 0]?.focus();
__privateMethod(this, _UUIRadioGroupElement_instances, fireChangeEvent_fn).call(this);
};
selectNextRadio_fn = function() {
this.value = __privateMethod(this, _UUIRadioGroupElement_instances, findAdjacentRadioElement_fn).call(this)?.value ?? "";
__privateGet(this, _radioElements)[__privateGet(this, _selected) ?? 0]?.focus();
__privateMethod(this, _UUIRadioGroupElement_instances, fireChangeEvent_fn).call(this);
};
fireChangeEvent_fn = function() {
this.pristine = false;
this.dispatchEvent(new UUIRadioGroupEvent(UUIRadioGroupEvent.CHANGE));
};
/**
* This is a static class field indicating that the element is can be used inside a native form and participate in its events. It may require a polyfill, check support here https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals. Read more about form controls here https://web.dev/more-capable-form-controls/
* @type {boolean}
*/
UUIRadioGroupElement.formAssociated = true;
UUIRadioGroupElement.styles = [
css`
:host {
display: inline-block;
padding-right: 3px;
border: 1px solid transparent;
border-radius: var(--uui-border-radius,3px);
}
:host(:not([pristine]):invalid),
/* polyfill support */
:host(:not([pristine])[internals-invalid]) {
border: 1px solid var(--uui-color-invalid-standalone,rgb(
174,
30,
71
));
}
`
];
__decorateClass([
property({ type: Boolean, reflect: true })
], UUIRadioGroupElement.prototype, "disabled", 2);
__decorateClass([
property({ type: Boolean, reflect: true })
], UUIRadioGroupElement.prototype, "readonly", 2);
UUIRadioGroupElement = __decorateClass([
defineElement("uui-radio-group")
], UUIRadioGroupElement);
export { UUIRadioElement, UUIRadioEvent, UUIRadioGroupElement, UUIRadioGroupEvent };