UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

268 lines (267 loc) • 13.4 kB
/*! 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!important;opacity:0!important;outline:none!important;padding:0!important;position:absolute!important;inset:0!important;transform:none!important;-webkit-appearance:none!important;z-index:-1!important}: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 };