UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

480 lines (479 loc) • 13.5 kB
/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. * v1.5.0-next.4 */ import { Build, h, Host } from "@stencil/core"; import { getElementDir } from "../../utils/dom"; import { afterConnectDefaultValueSet, connectForm, disconnectForm, HiddenFormInputSlot } from "../../utils/form"; import { connectInteractive, disconnectInteractive, updateHostInteraction } from "../../utils/interactive"; import { connectLabel, disconnectLabel } from "../../utils/label"; import { componentLoaded, setComponentLoaded, setUpLoadableComponent } from "../../utils/loadable"; /** * @slot - A slot for adding `calcite-segmented-control-item`s. */ export class SegmentedControl { constructor() { //-------------------------------------------------------------------------- // // Event Listeners // //-------------------------------------------------------------------------- this.handleClick = (event) => { if (this.disabled) { return; } if (event.target.localName === "calcite-segmented-control-item") { this.selectItem(event.target, true); } }; this.appearance = "solid"; this.disabled = false; this.form = undefined; this.required = false; this.layout = "horizontal"; this.name = undefined; this.scale = "m"; this.value = null; this.selectedItem = undefined; this.width = "auto"; } valueHandler(value) { const items = this.getItems(); items.forEach((item) => (item.checked = item.value === value)); } handleSelectedItemChange(newItem, oldItem) { this.value = newItem?.value; if (newItem === oldItem) { return; } const items = this.getItems(); const match = items.filter((item) => item === newItem).pop(); if (match) { this.selectItem(match); } else if (items[0]) { items[0].tabIndex = 0; } } //-------------------------------------------------------------------------- // // Lifecycle // //-------------------------------------------------------------------------- componentWillLoad() { setUpLoadableComponent(this); const items = this.getItems(); const lastChecked = items.filter((item) => item.checked).pop(); if (lastChecked) { this.selectItem(lastChecked); } else if (items[0]) { items[0].tabIndex = 0; } } componentDidLoad() { afterConnectDefaultValueSet(this, this.value); setComponentLoaded(this); } connectedCallback() { connectInteractive(this); connectLabel(this); connectForm(this); } disconnectedCallback() { disconnectInteractive(this); disconnectLabel(this); disconnectForm(this); } componentDidRender() { updateHostInteraction(this); } render() { return (h(Host, { onClick: this.handleClick, role: "radiogroup" }, h("slot", null), h(HiddenFormInputSlot, { component: this }))); } handleSelected(event) { event.preventDefault(); this.selectItem(event.target); 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.getItems(); 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; } } // -------------------------------------------------------------------------- // // Methods // // -------------------------------------------------------------------------- /** Sets focus on the component. */ async setFocus() { await componentLoaded(this); (this.selectedItem || this.getItems()[0])?.focus(); } //-------------------------------------------------------------------------- // // Private Methods // //-------------------------------------------------------------------------- onLabelClick() { this.setFocus(); } getItems() { return Array.from(this.el.querySelectorAll("calcite-segmented-control-item")); } selectItem(selected, emit = false) { if (selected === this.selectedItem) { return; } const items = this.getItems(); 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; if (emit) { this.calciteSegmentedControlChange.emit(); } } }); this.selectedItem = match; if (Build.isBrowser && match) { match.focus(); } } static get is() { return "calcite-segmented-control"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["segmented-control.scss"] }; } static get styleUrls() { return { "$": ["segmented-control.css"] }; } static get properties() { return { "appearance": { "type": "string", "mutable": false, "complexType": { "original": "Extract<\"outline\" | \"outline-fill\" | \"solid\", Appearance>", "resolved": "\"outline\" | \"outline-fill\" | \"solid\"", "references": { "Extract": { "location": "global" }, "Appearance": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the appearance style of the component." }, "attribute": "appearance", "reflect": true, "defaultValue": "\"solid\"" }, "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When `true`, interaction is prevented and the component is displayed with lower opacity." }, "attribute": "disabled", "reflect": true, "defaultValue": "false" }, "form": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The ID of the form that will be associated with the component.\n\nWhen not set, the component will be associated with its ancestor form element, if any." }, "attribute": "form", "reflect": true }, "required": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "When `true`, the component must have a value in order for the form to submit." }, "attribute": "required", "reflect": true, "defaultValue": "false" }, "layout": { "type": "string", "mutable": false, "complexType": { "original": "Layout", "resolved": "\"grid\" | \"horizontal\" | \"vertical\"", "references": { "Layout": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Defines the layout of the component." }, "attribute": "layout", "reflect": true, "defaultValue": "\"horizontal\"" }, "name": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the name of the component.\n\nRequired to pass the component's `value` on form submission." }, "attribute": "name", "reflect": true }, "scale": { "type": "string", "mutable": false, "complexType": { "original": "Scale", "resolved": "\"l\" | \"m\" | \"s\"", "references": { "Scale": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the size of the component." }, "attribute": "scale", "reflect": true, "defaultValue": "\"m\"" }, "value": { "type": "string", "mutable": true, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "The component's `selectedItem` value." }, "attribute": "value", "reflect": false, "defaultValue": "null" }, "selectedItem": { "type": "unknown", "mutable": true, "complexType": { "original": "HTMLCalciteSegmentedControlItemElement", "resolved": "HTMLCalciteSegmentedControlItemElement", "references": { "HTMLCalciteSegmentedControlItemElement": { "location": "global" } } }, "required": false, "optional": false, "docs": { "tags": [{ "name": "readonly", "text": undefined }], "text": "The component's selected item `HTMLElement`." } }, "width": { "type": "string", "mutable": false, "complexType": { "original": "Extract<\"auto\" | \"full\", Width>", "resolved": "\"auto\" | \"full\"", "references": { "Extract": { "location": "global" }, "Width": { "location": "import", "path": "../interfaces" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "Specifies the width of the component." }, "attribute": "width", "reflect": true, "defaultValue": "\"auto\"" } }; } static get events() { return [{ "method": "calciteSegmentedControlChange", "name": "calciteSegmentedControlChange", "bubbles": true, "cancelable": false, "composed": true, "docs": { "tags": [], "text": "Fires when the `calcite-segmented-control-item` selection changes." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }]; } static get methods() { return { "setFocus": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global" } }, "return": "Promise<void>" }, "docs": { "text": "Sets focus on the component.", "tags": [] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "value", "methodName": "valueHandler" }, { "propName": "selectedItem", "methodName": "handleSelectedItemChange" }]; } static get listeners() { return [{ "name": "calciteInternalSegmentedControlItemChange", "method": "handleSelected", "target": undefined, "capture": false, "passive": false }, { "name": "keydown", "method": "handleKeyDown", "target": undefined, "capture": false, "passive": false }]; } }