UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

477 lines (476 loc) • 15.1 kB
/*! * All material copyright ESRI, All Rights Reserved, unless otherwise specified. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details. */ import Sortable from "sortablejs"; import { Component, Element, Event, h, Listen, Method, Prop, State } from "@stencil/core"; import { CSS, ICON_TYPES } from "./resources"; import { calciteListFocusOutHandler, calciteListItemChangeHandler, calciteListItemValueChangeHandler, cleanUpObserver, deselectSiblingItems, deselectRemovedItems, getItemData, handleFilter, initialize, initializeObserver, keyDownHandler, mutationObserverCallback, removeItem, selectSiblings, setFocus, setUpItems, moveItemIndex } from "../pick-list/shared-list-logic"; import List from "../pick-list/shared-list-render"; import { createObserver } from "../../utils/observers"; import { updateHostInteraction } from "../../utils/interactive"; /** * @slot - A slot for adding `calcite-value-list-item` elements. Items are displayed as a vertical list. * @slot menu-actions - A slot for adding a button + menu combo for performing actions like sorting. */ export class ValueList { constructor() { // -------------------------------------------------------------------------- // // Properties // // -------------------------------------------------------------------------- /** * When true, disabled prevents interaction. This state shows items with lower opacity/grayed. */ this.disabled = false; /** * When true, the items will be sortable via drag and drop. */ this.dragEnabled = false; /** * When true, an input appears at the top of the list that can be used by end users to filter items in the list. */ this.filterEnabled = false; /** * When true, content is waiting to be loaded. This state shows a busy indicator. */ this.loading = false; /** * Multiple Works similar to standard radio buttons and checkboxes. * When true, a user can select multiple items at a time. * When false, only a single item can be selected at a time * and selecting a new item will deselect any other selected items. */ this.multiple = false; /** * When true and single-selection is enabled, the selection will change when navigating items via the keyboard. */ this.selectionFollowsFocus = false; // -------------------------------------------------------------------------- // // Private Properties // // -------------------------------------------------------------------------- this.selectedValues = new Map(); this.dataForFilter = []; this.lastSelectedItem = null; this.mutationObserver = createObserver("mutation", mutationObserverCallback.bind(this)); this.setFilterEl = (el) => { this.filterEl = el; }; this.deselectRemovedItems = deselectRemovedItems.bind(this); this.deselectSiblingItems = deselectSiblingItems.bind(this); this.selectSiblings = selectSiblings.bind(this); this.handleFilter = handleFilter.bind(this); this.getItemData = getItemData.bind(this); this.keyDownHandler = (event) => { const handleElement = event .composedPath() .find((item) => { var _a; return ((_a = item.dataset) === null || _a === void 0 ? void 0 : _a.jsHandle) !== undefined; }); const item = event .composedPath() .find((item) => { var _a; return ((_a = item.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === "calcite-value-list-item"; }); // Only trigger keyboard sorting when the internal drag handle is focused and activated if (!handleElement || !item.handleActivated) { keyDownHandler.call(this, event); return; } const { items } = this; if ((event.key !== "ArrowUp" && event.key !== "ArrowDown") || items.length <= 1) { return; } event.preventDefault(); const { el } = this; const nextIndex = moveItemIndex(this, item, event.key === "ArrowUp" ? "up" : "down"); if (nextIndex === items.length - 1) { el.appendChild(item); } else { const itemAtNextIndex = el.children[nextIndex]; const insertionReferenceItem = itemAtNextIndex === item.nextElementSibling ? itemAtNextIndex.nextElementSibling : itemAtNextIndex; el.insertBefore(item, insertionReferenceItem); } this.items = this.getItems(); this.calciteListOrderChange.emit(this.items.map(({ value }) => value)); requestAnimationFrame(() => handleElement.focus()); item.handleActivated = true; }; } // -------------------------------------------------------------------------- // // Lifecycle // // -------------------------------------------------------------------------- connectedCallback() { initialize.call(this); initializeObserver.call(this); } componentDidLoad() { this.setUpDragAndDrop(); } componentDidRender() { updateHostInteraction(this); } disconnectedCallback() { cleanUpObserver.call(this); this.cleanUpDragAndDrop(); } calciteListFocusOutHandler(event) { calciteListFocusOutHandler.call(this, event); } calciteListItemRemoveHandler(event) { removeItem.call(this, event); } calciteListItemChangeHandler(event) { calciteListItemChangeHandler.call(this, event); } calciteListItemPropsChangeHandler(event) { event.stopPropagation(); this.setUpFilter(); } calciteListItemValueChangeHandler(event) { calciteListItemValueChangeHandler.call(this, event); } // -------------------------------------------------------------------------- // // Private Methods // // -------------------------------------------------------------------------- getItems() { return Array.from(this.el.querySelectorAll("calcite-value-list-item")); } setUpItems() { setUpItems.call(this, "calcite-value-list-item"); } setUpFilter() { if (this.filterEnabled) { this.dataForFilter = this.getItemData(); } } setUpDragAndDrop() { this.cleanUpDragAndDrop(); if (!this.dragEnabled) { return; } this.sortable = Sortable.create(this.el, { dataIdAttr: "id", handle: `.${CSS.handle}`, draggable: "calcite-value-list-item", group: this.group, onSort: () => { this.items = Array.from(this.el.querySelectorAll("calcite-value-list-item")); const values = this.items.map((item) => item.value); this.calciteListOrderChange.emit(values); } }); } cleanUpDragAndDrop() { var _a; (_a = this.sortable) === null || _a === void 0 ? void 0 : _a.destroy(); this.sortable = null; } // -------------------------------------------------------------------------- // // Public Methods // // -------------------------------------------------------------------------- /** Returns the currently selected items */ async getSelectedItems() { return this.selectedValues; } /** Sets focus on the component. */ async setFocus(focusId) { return setFocus.call(this, focusId); } // -------------------------------------------------------------------------- // // Render Methods // // -------------------------------------------------------------------------- getIconType() { let type = null; if (this.dragEnabled) { type = ICON_TYPES.grip; } return type; } render() { return h(List, { onKeyDown: this.keyDownHandler, props: this }); } static get is() { return "calcite-value-list"; } static get encapsulation() { return "shadow"; } static get originalStyleUrls() { return { "$": ["value-list.scss"] }; } static get styleUrls() { return { "$": ["value-list.css"] }; } static get properties() { return { "disabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true, disabled prevents interaction. This state shows items with lower opacity/grayed." }, "attribute": "disabled", "reflect": true, "defaultValue": "false" }, "dragEnabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true, the items will be sortable via drag and drop." }, "attribute": "drag-enabled", "reflect": true, "defaultValue": "false" }, "filterEnabled": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true, an input appears at the top of the list that can be used by end users to filter items in the list." }, "attribute": "filter-enabled", "reflect": true, "defaultValue": "false" }, "filterPlaceholder": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Placeholder text for the filter input field." }, "attribute": "filter-placeholder", "reflect": true }, "group": { "type": "string", "mutable": false, "complexType": { "original": "string", "resolved": "string", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "The list's group identifier.\n\nTo drag elements from one list into another, both lists must have the same group value." }, "attribute": "group", "reflect": false }, "loading": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true, content is waiting to be loaded. This state shows a busy indicator." }, "attribute": "loading", "reflect": true, "defaultValue": "false" }, "multiple": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Multiple Works similar to standard radio buttons and checkboxes.\nWhen true, a user can select multiple items at a time.\nWhen false, only a single item can be selected at a time\nand selecting a new item will deselect any other selected items." }, "attribute": "multiple", "reflect": true, "defaultValue": "false" }, "selectionFollowsFocus": { "type": "boolean", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "When true and single-selection is enabled, the selection will change when navigating items via the keyboard." }, "attribute": "selection-follows-focus", "reflect": false, "defaultValue": "false" } }; } static get states() { return { "selectedValues": {}, "dataForFilter": {} }; } static get events() { return [{ "method": "calciteListChange", "name": "calciteListChange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when any of the item selections have changed." }, "complexType": { "original": "Map<string, HTMLCalciteValueListItemElement>", "resolved": "Map<string, HTMLCalciteValueListItemElement>", "references": { "Map": { "location": "global" }, "HTMLCalciteValueListItemElement": { "location": "global" } } } }, { "method": "calciteListOrderChange", "name": "calciteListOrderChange", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted when the order of the list has changed." }, "complexType": { "original": "any[]", "resolved": "any[]", "references": {} } }]; } static get methods() { return { "getSelectedItems": { "complexType": { "signature": "() => Promise<Map<string, HTMLCalciteValueListItemElement>>", "parameters": [], "references": { "Promise": { "location": "global" }, "Map": { "location": "global" }, "HTMLCalciteValueListItemElement": { "location": "global" } }, "return": "Promise<Map<string, HTMLCalciteValueListItemElement>>" }, "docs": { "text": "Returns the currently selected items", "tags": [] } }, "setFocus": { "complexType": { "signature": "(focusId?: ListFocusId) => Promise<void>", "parameters": [{ "tags": [], "text": "" }], "references": { "Promise": { "location": "global" }, "ListFocusId": { "location": "import", "path": "../pick-list/shared-list-logic" } }, "return": "Promise<void>" }, "docs": { "text": "Sets focus on the component.", "tags": [] } } }; } static get elementRef() { return "el"; } static get listeners() { return [{ "name": "focusout", "method": "calciteListFocusOutHandler", "target": undefined, "capture": false, "passive": false }, { "name": "calciteListItemRemove", "method": "calciteListItemRemoveHandler", "target": undefined, "capture": false, "passive": false }, { "name": "calciteListItemChange", "method": "calciteListItemChangeHandler", "target": undefined, "capture": false, "passive": false }, { "name": "calciteListItemPropsChange", "method": "calciteListItemPropsChangeHandler", "target": undefined, "capture": false, "passive": false }, { "name": "calciteListItemValueChange", "method": "calciteListItemValueChangeHandler", "target": undefined, "capture": false, "passive": false }]; } }