UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

228 lines (227 loc) • 9.48 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 { isServer, html } from "lit"; import { LitElement, createEvent, stringOrBoolean, safeClassMap } from "@arcgis/lumina"; import { g as getElementDir, s as slotChangeGetAssignedElements } from "../../chunks/dom.js"; import { c as connectForm, a as afterConnectDefaultValueSet, 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 } from "../../chunks/label.js"; import { c as componentFocusable } from "../../chunks/component.js"; import { V as Validation } from "../../chunks/Validation.js"; import { css } from "@lit/reactive-element/css-tag.js"; const CSS = { itemWrapper: "item-wrapper" }; const IDS = { validationMessage: "segmentedControlValidationMessage" }; 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:flex;flex-direction:column}.item-wrapper{display:flex;background-color:var(--calcite-color-foreground-1);inline-size:fit-content;outline:1px solid var(--calcite-segmented-control-border-color, var(--calcite-color-border-input));outline-offset:-1px}:host([appearance=outline])>.item-wrapper{background-color:transparent}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.interaction-container{display:contents}:host([layout=vertical])>.item-wrapper{flex-direction:column;align-items:flex-start;align-self:flex-start}:host([width=full])>.item-wrapper{inline-size:100%;min-inline-size:fit-content}:host([width=full])>.item-wrapper ::slotted(calcite-segmented-control-item){flex:1 1 auto}:host([width=full][layout=vertical])>.item-wrapper ::slotted(calcite-segmented-control-item){justify-content:flex-start}.validation-container{display:flex;flex-direction:column;align-items:flex-start;align-self:stretch}:host([scale=m]) .validation-container,:host([scale=l]) .validation-container{padding-block-start:.5rem}:host([scale=s]) .validation-container{padding-block-start:.25rem}::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 SegmentedControl extends LitElement { constructor() { super(); this.items = []; this.appearance = "solid"; this.disabled = false; this.layout = "horizontal"; this.required = false; this.scale = "m"; this.status = "idle"; this.validity = { valid: false, badInput: false, customError: false, patternMismatch: false, rangeOverflow: false, rangeUnderflow: false, stepMismatch: false, tooLong: false, tooShort: false, typeMismatch: false, valueMissing: false }; this.value = null; this.width = "auto"; this.calciteSegmentedControlChange = createEvent({ cancelable: false }); this.listen("calciteInternalSegmentedControlItemChange", this.handleSelected); this.listen("keydown", this.handleKeyDown); this.listen("click", this.handleClick); } static { this.properties = { appearance: [3, {}, { reflect: true }], disabled: [7, {}, { reflect: true, type: Boolean }], form: [3, {}, { reflect: true }], layout: [3, {}, { reflect: true }], name: [3, {}, { reflect: true }], required: [7, {}, { reflect: true, type: Boolean }], scale: [3, {}, { reflect: true }], selectedItem: [0, {}, { attribute: false }], status: [3, {}, { reflect: true }], validationIcon: [3, { converter: stringOrBoolean }, { reflect: true }], validationMessage: 1, validity: [0, {}, { attribute: false }], value: 1, width: [3, {}, { reflect: true }] }; } static { this.styles = styles; } async setFocus() { await componentFocusable(this); (this.selectedItem || this.items[0])?.focus(); } connectedCallback() { super.connectedCallback(); connectLabel(this); connectForm(this); } willUpdate(changes) { if (changes.has("appearance") && (this.hasUpdated || this.appearance !== "solid") || changes.has("layout") && (this.hasUpdated || this.layout !== "horizontal") || changes.has("scale") && (this.hasUpdated || this.scale !== "m")) { this.handleItemPropChange(); } if (changes.has("value") && (this.hasUpdated || this.value !== null)) { this.valueHandler(this.value); } if (changes.has("selectedItem")) { this.handleSelectedItemChange(this.selectedItem, changes.get("selectedItem")); } } updated() { updateHostInteraction(this); } loaded() { afterConnectDefaultValueSet(this, this.value); } disconnectedCallback() { super.disconnectedCallback(); disconnectLabel(this); disconnectForm(this); } valueHandler(value) { const { items } = this; items.forEach((item) => item.checked = item.value === value); } handleSelectedItemChange(newItem, oldItem) { this.value = newItem?.value; if (newItem === oldItem) { return; } const { items } = this; const match = items.filter((item) => item === newItem).pop(); if (match) { this.selectItem(match); } else if (items[0]) { items[0].tabIndex = 0; } } handleClick(event) { if (this.disabled) { return; } if (event.target.localName === "calcite-segmented-control-item") { this.selectItem(event.target, true); } } handleSelected(event) { event.preventDefault(); const el = event.target; if (el.checked) { this.selectItem(el); } event.stopPropagation(); } handleKeyDown(event) { const keys = ["ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", " "]; const { key } = event; const { el, selectedItem } = this; if (keys.indexOf(key) === -1) { return; } let adjustedKey = key; if (getElementDir(el) === "rtl") { if (key === "ArrowRight") { adjustedKey = "ArrowLeft"; } if (key === "ArrowLeft") { adjustedKey = "ArrowRight"; } } const { items } = this; let selectedIndex = -1; items.forEach((item, index) => { if (item === selectedItem) { selectedIndex = index; } }); switch (adjustedKey) { case "ArrowLeft": case "ArrowUp": { event.preventDefault(); const previous = selectedIndex < 1 ? items[items.length - 1] : items[selectedIndex - 1]; this.selectItem(previous, true); return; } case "ArrowRight": case "ArrowDown": { event.preventDefault(); const next = selectedIndex === -1 ? items[1] : items[selectedIndex + 1] || items[0]; this.selectItem(next, true); return; } case " ": event.preventDefault(); this.selectItem(event.target, true); return; default: return; } } handleItemPropChange() { const { items } = this; items.forEach((item) => { item.appearance = this.appearance; item.layout = this.layout; item.scale = this.scale; }); } handleSelectedItem() { const { items } = this; const lastChecked = items.filter((item) => item.checked).pop(); if (lastChecked) { this.selectItem(lastChecked); } else if (items[0]) { items[0].tabIndex = 0; } } async handleDefaultSlotChange(event) { const items = slotChangeGetAssignedElements(event).filter((el) => el.matches("calcite-segmented-control-item")); await Promise.all(items.map((item) => item.componentOnReady())); this.items = items; this.handleSelectedItem(); this.handleItemPropChange(); } onLabelClick() { this.setFocus(); } async selectItem(selected, emit = false) { if (selected === this.selectedItem) { return; } const { items } = this; let match = null; items.forEach((item) => { const matches = item === selected; if (matches && !item.checked || !matches && item.checked) { item.checked = matches; } item.tabIndex = matches ? 0 : -1; if (matches) { match = item; } }); this.selectedItem = match; if (match && emit) { await this.updateComplete; this.calciteSegmentedControlChange.emit(); } if (!isServer && match) { match.focus(); } } render() { this.el.role = "radiogroup"; return html`<div aria-errormessage=${IDS.validationMessage} .ariaInvalid=${this.status === "invalid"} class=${safeClassMap(CSS.itemWrapper)}>${InteractiveContainer({ disabled: this.disabled, children: html`<slot @slotchange=${this.handleDefaultSlotChange}></slot>${HiddenFormInputSlot({ component: this })}` })}</div>${this.validationMessage && this.status === "invalid" ? Validation({ icon: this.validationIcon, id: IDS.validationMessage, message: this.validationMessage, scale: this.scale, status: this.status }) : null}`; } } customElement("calcite-segmented-control", SegmentedControl); export { SegmentedControl };