UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

268 lines (267 loc) • 15.4 kB
/* COPYRIGHT Esri - https://js.arcgis.com/5.0/LICENSE.txt */ import { c as customElement } from "../../chunks/runtime.js"; import { css, nothing, html } from "lit"; import { LitElement, createEvent, safeClassMap } from "@arcgis/lumina"; import { createRef, ref } from "lit/directives/ref.js"; import { g as getRoundRobinIndex } from "../../chunks/array.js"; import { g as getElementDir } from "../../chunks/dom.js"; import { c as connectForm, d as disconnectForm, H as HiddenFormInputSlot } from "../../chunks/form.js"; import { c as connectLabel, d as disconnectLabel, g as getLabelText } from "../../chunks/label.js"; import { I as InternalLabel } from "../../chunks/InternalLabel.js"; import { u as useSetFocus } from "../../chunks/useSetFocus.js"; import { u as useInteractive } from "../../chunks/useInteractive.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;display:flex}: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-hover))}:host([hovered]) .radio:active,:host(:not([checked])[focused]:not([disabled])) .radio:active{box-shadow:inset 0 0 0 var(--calcite-border-width-md) var(--calcite-color-brand-press)}:host([focused]) .radio{outline:var(--calcite-border-width-md) solid var(--calcite-color-focus, var(--calcite-ui-focus-color, var(--calcite-color-brand)));outline-offset:calc(var(--calcite-spacing-base) * calc(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-size-fixed-md))}:host([scale=m]){--calcite-internal-radio-size: var(--calcite-radio-button-size, var(--calcite-size-fixed-md-plus))}:host([scale=l]){--calcite-internal-radio-size: var(--calcite-radio-button-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:CanvasText;display:block}}.internal-label-alignment--center{align-items:center}.internal-label-alignment--end{align-items:end}.internal-label--container{display:flex;justify-content:space-between;color:var(--calcite-color-text-1)}.internal-label-required--indicator{font-weight:var(--calcite-font-weight-medium);color:var(--calcite-color-status-danger);padding-inline:var(--calcite-spacing-base)}.internal-label-required--indicator:hover{cursor:help}.internal-label--text{line-height:1}:host([scale=s]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-xxs)}:host([scale=s]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-sm)}:host([scale=s]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-sm)}:host([scale=s]) .internal-label--text{font-size:var(--calcite-font-size--2)}:host([scale=m]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-sm)}:host([scale=m]) .internal-label--text{font-size:var(--calcite-font-size--1)}:host([scale=l]) .internal-label-spacing--bottom{margin-block-end:var(--calcite-spacing-sm)}:host([scale=l]) .internal-label-spacing-inline--end{margin-inline-end:var(--calcite-spacing-md)}:host([scale=l]) .internal-label-spacing-inline--start{margin-inline-start:var(--calcite-spacing-md)}:host([scale=l]) .internal-label--text{font-size:var(--calcite-font-size-0)}::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.containerRef = createRef(); this.focusSetter = useSetFocus()(this); this.interactiveContainer = useInteractive(this); 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, labelText: 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(options) { return this.focusSetter(() => this.containerRef.value, options); } 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(); } } 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(); }); } } 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; radioButtons.some((item, index) => { if (item.checked) { currentIndex = index; return true; } }); switch (adjustedKey) { case "ArrowLeft": case "ArrowUp": event.preventDefault(); this.selectItem(radioButtons, this.getNextNonDisabledIndex(radioButtons, currentIndex, "left")); return; case "ArrowRight": case "ArrowDown": event.preventDefault(); this.selectItem(radioButtons, this.getNextNonDisabledIndex(radioButtons, currentIndex, "right")); return; default: return; } } getNextNonDisabledIndex(radioButtons, startIndex, dir) { const totalButtons = radioButtons.length; const offset = dir === "left" ? -1 : 1; let selectIndex = getRoundRobinIndex(startIndex + offset, totalButtons); while (radioButtons[selectIndex].disabled) { selectIndex = getRoundRobinIndex(selectIndex + offset, totalButtons); } return selectIndex; } onContainerBlur() { this.focused = false; this.calciteInternalRadioButtonBlur.emit(); } onContainerFocus() { if (!this.disabled) { this.focused = true; this.calciteInternalRadioButtonFocus.emit(); } } render() { const tabIndex = this.getTabIndex(); return this.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.containerRef)}><div class=${safeClassMap(CSS.radio)}></div>${this.labelText && InternalLabel({ labelText: this.labelText, spacingInlineStart: true }) || ""}</div>${HiddenFormInputSlot({ component: this })}` }); } } customElement("calcite-radio-button", RadioButton); export { RadioButton };