@esri/calcite-components
Version:
Web Components for Esri's Calcite Design System.
480 lines (479 loc) • 13.5 kB
JavaScript
/*!
* 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
}];
}
}