UNPKG

@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
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 };