@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
228 lines (227 loc) • 9.48 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 { 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 ;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 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
};