@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
268 lines (267 loc) • 13.4 kB
JavaScript
/*! All material copyright ESRI, All Rights Reserved, unless otherwise specified.
See https://github.com/Esri/calcite-design-system/blob/dev/LICENSE.md for details.
v3.2.1 */
import { c as customElement } from "../../chunks/runtime.js";
import { ref } from "lit-html/directives/ref.js";
import { nothing, html } from "lit";
import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina";
import { g as getRoundRobinIndex } from "../../chunks/array.js";
import { b as focusElement, g as getElementDir } from "../../chunks/dom.js";
import { c as connectForm, d as disconnectForm, H as HiddenFormInputSlot } from "../../chunks/form.js";
import { u as updateHostInteraction, I as InteractiveContainer } from "../../chunks/interactive.js";
import { c as connectLabel, d as disconnectLabel, g as getLabelText } from "../../chunks/label.js";
import { c as componentFocusable } from "../../chunks/component.js";
import { css } from "@lit/reactive-element/css-tag.js";
const CSS = {
container: "container",
radio: "radio"
};
const styles = css`:host([disabled]){cursor:default;-webkit-user-select:none;user-select:none;opacity:var(--calcite-opacity-disabled)}:host([disabled]) *,:host([disabled]) ::slotted(*){pointer-events:none}:host{display:block;cursor:pointer}:host .container{position:relative;outline:2px solid transparent;outline-offset:2px}:host .radio{cursor:pointer;outline-color:transparent;transition-property:background-color,block-size,border-color,box-shadow,color,inset-block-end,inset-block-start,inset-inline-end,inset-inline-start,inset-size,opacity,outline-color,transform;transition-duration:var(--calcite-animation-timing);transition-timing-function:ease-in-out;border-radius:var(--calcite-radio-button-corner-radius, var(--calcite-corner-radius-pill));background-color:var(--calcite-radio-button-background-color, var(--calcite-color-foreground-1));box-shadow:inset 0 0 0 var(--calcite-border-width-sm) var(--calcite-radio-button-border-color, var(--calcite-color-border-input))}:host([hovered]) .radio,:host(:not([checked])[focused]:not([disabled])) .radio{box-shadow:inset 0 0 0 var(--calcite-border-width-md) var(--calcite-radio-button-border-color, var(--calcite-color-brand))}:host([focused]) .radio{outline:2px solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(2px*(1 - (2*clamp(0,var(--calcite-offset-invert-focus),1))))}:host([disabled]) .radio{cursor:default;opacity:var(--calcite-opacity-disabled)}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}:host([hovered][disabled]) .radio{box-shadow:inset 0 0 0 var(--calcite-border-width-sm) var(--calcite-radio-button-border-color, var(--calcite-color-border-input))}:host([scale=s]){--calcite-internal-radio-size: var( --calcite-radio-button-size, var(--calcite-radio-size, var(--calcite-size-fixed-md)) )}:host([scale=m]){--calcite-internal-radio-size: var( --calcite-radio-button-size, var(--calcite-radio-size, var(--calcite-size-fixed-md-plus)) )}:host([scale=l]){--calcite-internal-radio-size: var( --calcite-radio-button-size, var(--calcite-radio-size, var(--calcite-size-fixed-lg)) )}.radio{block-size:var(--calcite-internal-radio-size);inline-size:var(--calcite-internal-radio-size);size:var(--calcite-internal-radio-size)}:host([scale=s][checked]) .radio,:host([hovered][scale=s][checked][disabled]) .radio{box-shadow:inset 0 0 0 var(--calcite-border-width-lg) var(--calcite-radio-button-border-color, var(--calcite-color-brand))}:host([scale=s][focused][checked]:not([disabled])) .radio{box-shadow:inset 0 0 0 var(--calcite-border-width-lg) var(--calcite-radio-button-border-color, var(--calcite-color-brand)),0 0 0 2px var(--calcite-radio-button-background-color, var(--calcite-color-foreground-1))}:host([scale=m][checked]) .radio,:host([hovered][scale=m][checked][disabled]) .radio{box-shadow:inset 0 0 0 5px var(--calcite-radio-button-border-color, var(--calcite-color-brand))}:host([scale=m][focused][checked]:not([disabled])) .radio{box-shadow:inset 0 0 0 5px var(--calcite-radio-button-border-color, var(--calcite-color-brand)),0 0 0 2px var(--calcite-radio-button-background-color, var(--calcite-color-foreground-1))}:host([scale=l][checked]) .radio,:host([hovered][scale=l][checked][disabled]) .radio{box-shadow:inset 0 0 0 6px var(--calcite-radio-button-border-color, var(--calcite-color-brand))}:host([scale=l][focused][checked]:not([disabled])) .radio{box-shadow:inset 0 0 0 6px var(--calcite-radio-button-border-color, var(--calcite-color-brand)),0 0 0 2px var(--calcite-radio-button-background-color, var(--calcite-color-foreground-1))}@media (forced-colors: active){:host([checked]) .radio:after,:host([checked][disabled]) .radio:after{content:"";inline-size:var(--calcite-internal-radio-size);block-size:var(--calcite-internal-radio-size);background-color:windowText;display:block}}::slotted(input[slot=hidden-form-input]){margin:0 ;opacity:0 ;outline:none ;padding:0 ;position:absolute ;inset:0 ;transform:none ;-webkit-appearance:none ;z-index:-1 }:host([hidden]){display:none}[hidden]{display:none}`;
class RadioButton extends LitElement {
constructor() {
super();
this.checked = false;
this.disabled = false;
this.focused = false;
this.hovered = false;
this.required = false;
this.scale = "m";
this.calciteInternalRadioButtonBlur = createEvent({ cancelable: false });
this.calciteInternalRadioButtonCheckedChange = createEvent({ cancelable: false });
this.calciteInternalRadioButtonFocus = createEvent({ cancelable: false });
this.calciteRadioButtonChange = createEvent({ cancelable: false });
this.listen("pointerenter", this.pointerEnterHandler);
this.listen("pointerleave", this.pointerLeaveHandler);
this.listen("click", this.clickHandler);
this.listen("keydown", this.handleKeyDown);
}
static {
this.properties = { checked: [7, {}, { reflect: true, type: Boolean }], disabled: [7, {}, { reflect: true, type: Boolean }], focused: [7, {}, { reflect: true, type: Boolean }], form: [3, {}, { reflect: true }], hovered: [7, {}, { reflect: true, type: Boolean }], label: 1, name: [3, {}, { reflect: true }], required: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], value: 1 };
}
static {
this.styles = styles;
}
async emitCheckedChange() {
this.calciteInternalRadioButtonCheckedChange.emit();
}
async setFocus() {
await componentFocusable(this);
if (!this.disabled) {
focusElement(this.containerEl);
}
}
connectedCallback() {
this.rootNode = this.el.getRootNode();
if (this.name) {
this.checkLastRadioButton();
}
connectLabel(this);
connectForm(this);
this.updateTabIndexOfOtherRadioButtonsInGroup();
super.connectedCallback();
}
willUpdate(changes) {
if (this.hasUpdated && changes.has("checked")) {
this.checkedChanged(this.checked);
}
if (changes.has("disabled") && (this.hasUpdated || this.disabled !== false)) {
this.updateTabIndexOfOtherRadioButtonsInGroup();
}
if (changes.has("name")) {
this.checkLastRadioButton();
}
}
updated() {
updateHostInteraction(this);
}
loaded() {
if (this.focused && !this.disabled) {
this.setFocus();
}
}
disconnectedCallback() {
super.disconnectedCallback();
disconnectLabel(this);
disconnectForm(this);
this.updateTabIndexOfOtherRadioButtonsInGroup();
}
checkedChanged(newChecked) {
if (newChecked) {
this.uncheckOtherRadioButtonsInGroup();
}
this.calciteInternalRadioButtonCheckedChange.emit();
}
syncHiddenFormInput(input) {
input.type = "radio";
}
selectItem(items, selectedIndex) {
items[selectedIndex].click();
}
queryButtons() {
return Array.from(this.rootNode.querySelectorAll("calcite-radio-button:not([hidden])")).filter((radioButton) => radioButton.name === this.name);
}
isFocusable() {
const radioButtons = this.queryButtons();
const firstFocusable = radioButtons.find((radioButton) => !radioButton.disabled);
const checked = radioButtons.find((radioButton) => radioButton.checked);
return firstFocusable === this.el && !checked;
}
check() {
if (this.disabled) {
return;
}
this.focused = true;
this.setFocus();
if (this.checked) {
return;
}
this.uncheckAllRadioButtonsInGroup();
this.checked = true;
this.calciteRadioButtonChange.emit();
}
clickHandler() {
if (this.disabled) {
return;
}
this.check();
}
onLabelClick(event) {
if (this.disabled || this.el.hidden) {
return;
}
const label = event.currentTarget;
const radioButton = label.for ? this.rootNode.querySelector(`calcite-radio-button[id="${label.for}"]`) : label.querySelector(`calcite-radio-button[name="${this.name}"]`);
if (!radioButton) {
return;
}
radioButton.focused = true;
this.setFocus();
if (radioButton.checked) {
return;
}
this.uncheckOtherRadioButtonsInGroup();
radioButton.checked = true;
this.calciteRadioButtonChange.emit();
}
checkLastRadioButton() {
const radioButtons = this.queryButtons();
const checkedRadioButtons = radioButtons.filter((radioButton) => radioButton.checked);
if (checkedRadioButtons?.length > 1) {
const lastCheckedRadioButton = checkedRadioButtons[checkedRadioButtons.length - 1];
checkedRadioButtons.filter((checkedRadioButton) => checkedRadioButton !== lastCheckedRadioButton).forEach((checkedRadioButton) => {
checkedRadioButton.checked = false;
checkedRadioButton.emitCheckedChange();
});
}
}
setContainerEl(el) {
this.containerEl = el;
}
uncheckAllRadioButtonsInGroup() {
const radioButtons = this.queryButtons();
radioButtons.forEach((radioButton) => {
if (radioButton.checked) {
radioButton.checked = false;
radioButton.focused = false;
}
});
}
uncheckOtherRadioButtonsInGroup() {
const radioButtons = this.queryButtons();
const otherRadioButtons = radioButtons.filter((radioButton) => radioButton !== this.el);
otherRadioButtons.forEach((otherRadioButton) => {
if (otherRadioButton.checked) {
otherRadioButton.checked = false;
otherRadioButton.focused = false;
}
});
}
updateTabIndexOfOtherRadioButtonsInGroup() {
const radioButtons = this.queryButtons();
const otherFocusableRadioButtons = radioButtons.filter((radioButton) => radioButton !== this.el && !radioButton.disabled);
otherFocusableRadioButtons.forEach((radioButton) => {
radioButton.manager?.component.requestUpdate();
});
}
getTabIndex() {
if (this.disabled) {
return void 0;
}
return this.checked || this.isFocusable() ? 0 : -1;
}
pointerEnterHandler() {
if (this.disabled) {
return;
}
this.hovered = true;
}
pointerLeaveHandler() {
if (this.disabled) {
return;
}
this.hovered = false;
}
handleKeyDown(event) {
const keys = ["ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", " "];
const { key } = event;
const { el } = this;
if (keys.indexOf(key) === -1) {
return;
}
if (key === " ") {
this.check();
event.preventDefault();
return;
}
let adjustedKey = key;
if (getElementDir(el) === "rtl") {
if (key === "ArrowRight") {
adjustedKey = "ArrowLeft";
}
if (key === "ArrowLeft") {
adjustedKey = "ArrowRight";
}
}
const radioButtons = Array.from(this.rootNode.querySelectorAll("calcite-radio-button:not([hidden])")).filter((radioButton) => radioButton.name === this.name);
let currentIndex = 0;
const radioButtonsLength = radioButtons.length;
radioButtons.some((item, index) => {
if (item.checked) {
currentIndex = index;
return true;
}
});
switch (adjustedKey) {
case "ArrowLeft":
case "ArrowUp":
event.preventDefault();
this.selectItem(radioButtons, getRoundRobinIndex(Math.max(currentIndex - 1, -1), radioButtonsLength));
return;
case "ArrowRight":
case "ArrowDown":
event.preventDefault();
this.selectItem(radioButtons, getRoundRobinIndex(currentIndex + 1, radioButtonsLength));
return;
default:
return;
}
}
onContainerBlur() {
this.focused = false;
this.calciteInternalRadioButtonBlur.emit();
}
onContainerFocus() {
if (!this.disabled) {
this.focused = true;
this.calciteInternalRadioButtonFocus.emit();
}
}
render() {
const tabIndex = this.getTabIndex();
return InteractiveContainer({ disabled: this.disabled, children: html`<div .ariaChecked=${this.checked} .ariaLabel=${getLabelText(this)} class=${safeClassMap(CSS.container)} @blur=${this.onContainerBlur} @focus=${this.onContainerFocus} role=radio tabindex=${tabIndex ?? nothing} ${ref(this.setContainerEl)}><div class=${safeClassMap(CSS.radio)}></div></div>${HiddenFormInputSlot({ component: this })}` });
}
}
customElement("calcite-radio-button", RadioButton);
export {
RadioButton
};