UNPKG

@esri/calcite-components

Version:

Web Components for Esri's Calcite Design System.

294 lines (293 loc) • 11.6 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 { html } from "lit"; import { LitElement, createEvent, setAttribute } from "@arcgis/lumina"; import { b as focusElement, n as nodeListToArray, s as slotChangeGetAssignedElements, t as toAriaBoolean } from "../../chunks/dom.js"; import { css } from "@lit/reactive-element/css-tag.js"; function isTreeItem(element) { return element?.tagName === "CALCITE-TREE-ITEM"; } function getTraversableItems(root) { return Array.from(root.querySelectorAll("calcite-tree-item:not([disabled])")).filter( (item) => { let currentItem = item; while (currentItem !== root && currentItem !== void 0) { const parent = currentItem.parentElement; const traversable = !isTreeItem(parent) || !parent.hasChildren || parent.expanded; if (!traversable) { return false; } currentItem = currentItem.parentElement; } return true; } ); } const styles = css`:host{display:block}:host(:focus){outline:2px solid transparent;outline-offset:2px}:host([hidden]){display:none}[hidden]{display:none}`; class Tree extends LitElement { constructor() { super(); this.items = []; this.lines = false; this.parentExpanded = false; this.scale = "m"; this.selectedItems = []; this.selectionMode = "single"; this.calciteTreeSelect = createEvent({ cancelable: false }); this.listen("focus", this.onFocus); this.listen("focusin", this.onFocusIn); this.listen("focusout", this.onFocusOut); this.listen("calciteInternalTreeItemSelect", this.onInternalTreeItemSelect); this.listen("keydown", this.keyDownHandler); } static { this.properties = { child: [7, {}, { reflect: true, type: Boolean }], lines: [7, {}, { reflect: true, type: Boolean }], parentExpanded: [5, {}, { type: Boolean }], scale: [3, {}, { reflect: true }], selectedItems: [0, {}, { attribute: false }], selectionMode: [3, {}, { reflect: true }] }; } static { this.styles = styles; } willUpdate(changes) { if (changes.has("parentExpanded") && (this.hasUpdated || this.parentExpanded !== false)) { this.updateItems(); } const parent = this.el.parentElement?.closest("calcite-tree"); this.lines = parent ? parent.lines : this.lines; this.scale = parent ? parent.scale : this.scale; this.selectionMode = parent ? parent.selectionMode : this.selectionMode; this.child = !!parent; } onFocus() { if (!this.child) { const focusTarget = this.el.querySelector("calcite-tree-item[selected]:not([disabled])") || this.el.querySelector("calcite-tree-item:not([disabled])"); focusElement(focusTarget); } } onFocusIn(event) { const focusedFromRootOrOutsideTree = event.relatedTarget === this.el || !this.el.contains(event.relatedTarget); if (focusedFromRootOrOutsideTree) { this.el.removeAttribute("tabindex"); } } onFocusOut(event) { const willFocusOutsideTree = !this.el.contains(event.relatedTarget); if (willFocusOutsideTree) { this.el.tabIndex = this.getRootTabIndex(); } } onInternalTreeItemSelect(event) { if (this.child) { return; } const target = event.target; const childItems = nodeListToArray(target.querySelectorAll("calcite-tree-item")); event.preventDefault(); event.stopPropagation(); if (this.selectionMode === "ancestors") { this.updateAncestorTree(event); return; } const isNoneSelectionMode = this.selectionMode === "none"; const shouldSelect = this.selectionMode !== null && (!target.hasChildren || target.hasChildren && (this.selectionMode === "children" || this.selectionMode === "multichildren")); const shouldDeselectAllChildren = this.selectionMode === "multichildren" && target.hasChildren; const shouldModifyToCurrentSelection = !isNoneSelectionMode && event.detail.modifyCurrentSelection && (this.selectionMode === "multiple" || this.selectionMode === "multichildren"); const shouldClearCurrentSelection = !shouldModifyToCurrentSelection && ((this.selectionMode === "single" || this.selectionMode === "multiple") && childItems.length <= 0 || this.selectionMode === "children" || this.selectionMode === "multichildren" || this.selectionMode === "single-persist" && !target.hasChildren); const shouldUpdateExpand = ["multiple", "none", "single", "single-persist"].includes(this.selectionMode) && target.hasChildren; const targetItems = []; if (shouldSelect) { targetItems.push(target); } if (shouldClearCurrentSelection) { const selectedItems = nodeListToArray(this.el.querySelectorAll("calcite-tree-item[selected]")); selectedItems.forEach((treeItem) => { if (!targetItems.includes(treeItem)) { treeItem.selected = false; } }); } if (shouldUpdateExpand && ["multiple", "none", "single", "single-persist"].includes(this.selectionMode)) { target.expanded = !target.expanded; } if (shouldDeselectAllChildren) { childItems.forEach((item) => { item.selected = false; if (item.hasChildren) { item.expanded = false; } }); } if (shouldModifyToCurrentSelection) { window.getSelection().removeAllRanges(); } if (shouldModifyToCurrentSelection && target.selected) { targetItems.forEach((treeItem) => { if (!treeItem.disabled) { treeItem.selected = false; } }); } else if (!isNoneSelectionMode) { targetItems.forEach((treeItem) => { if (!treeItem.disabled) { treeItem.selected = this.selectionMode !== "single" || !treeItem.selected; } }); } this.selectedItems = isNoneSelectionMode ? [] : nodeListToArray(this.el.querySelectorAll("calcite-tree-item")).filter((i) => i.selected); this.calciteTreeSelect.emit(); event.stopPropagation(); } keyDownHandler(event) { if (this.child) { return; } const root = this.el; const target = event.target; const supportedKeys = ["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp", "Home", "End", "Tab"]; if (!(isTreeItem(target) && this.el.contains(target)) || !supportedKeys.includes(event.key)) { return; } const traversableItems = getTraversableItems(root); if (event.key === "Tab") { traversableItems.forEach((item) => item.tabIndex = -1); return; } if (event.key === "ArrowDown") { const currentItemIndex = traversableItems.indexOf(target); const nextItem = traversableItems[currentItemIndex + 1]; nextItem?.focus(); event.preventDefault(); return; } if (event.key === "ArrowUp") { const currentItemIndex = traversableItems.indexOf(target); const previousItem = traversableItems[currentItemIndex - 1]; previousItem?.focus(); event.preventDefault(); return; } if (event.key === "ArrowLeft") { if (target.hasChildren && target.expanded) { target.expanded = false; event.preventDefault(); return; } const rootToItemPath = traversableItems.slice(0, traversableItems.indexOf(target)).reverse(); const parentItem = rootToItemPath.find((item) => item.depth === target.depth - 1); parentItem?.focus(); event.preventDefault(); return; } if (event.key === "ArrowRight") { if (!target.disabled && target.hasChildren) { if (!target.expanded) { target.expanded = true; event.preventDefault(); } else { const currentItemIndex = traversableItems.indexOf(target); const nextItem = traversableItems[currentItemIndex + 1]; nextItem?.focus(); event.preventDefault(); } } return; } if (event.key === "Home") { const firstNode = traversableItems.shift(); if (firstNode) { firstNode.focus(); event.preventDefault(); } return; } if (event.key === "End") { const lastNode = traversableItems.pop(); if (lastNode) { lastNode.focus(); event.preventDefault(); } return; } } updateAncestorTree(event) { const item = event.target; const updateItem = event.detail.updateItem; if (item.disabled || item.indeterminate && !updateItem) { return; } const ancestors = []; let parent = item.parentElement.closest("calcite-tree-item"); while (parent) { ancestors.push(parent); parent = parent.parentElement.closest("calcite-tree-item"); } const childItems = Array.from(item.querySelectorAll("calcite-tree-item:not([disabled])")); const childItemsWithNoChildren = childItems.filter((child) => !child.hasChildren); const childItemsWithChildren = childItems.filter((child) => child.hasChildren); let futureSelected; if (updateItem) { futureSelected = item.hasChildren ? !(item.selected || item.indeterminate) : !item.selected; } else { futureSelected = item.selected; } childItemsWithNoChildren.forEach((el) => { el.selected = futureSelected; el.indeterminate = false; }); function updateItemState(childItems2, item2) { const selected = childItems2.filter((child) => child.selected); const unselected = childItems2.filter((child) => !child.selected); item2.selected = selected.length === childItems2.length; item2.indeterminate = selected.length > 0 && unselected.length > 0; } childItemsWithChildren.reverse().forEach((el) => { const directChildItems = Array.from(el.querySelectorAll(":scope > calcite-tree > calcite-tree-item")); updateItemState(directChildItems, el); }); if (updateItem) { if (item.hasChildren) { updateItemState(childItems, item); } else { item.selected = futureSelected; item.indeterminate = false; } } ancestors.forEach((ancestor) => { const descendants = nodeListToArray(ancestor.querySelectorAll("calcite-tree-item")); const activeDescendants = descendants.filter((el) => el.selected); if (activeDescendants.length === 0) { ancestor.selected = false; ancestor.indeterminate = false; return; } const indeterminate = activeDescendants.length < descendants.length; ancestor.indeterminate = indeterminate; ancestor.selected = !indeterminate; }); this.selectedItems = nodeListToArray(this.el.querySelectorAll("calcite-tree-item")).filter((i) => i.selected); if (updateItem) { this.calciteTreeSelect.emit(); } } updateItems() { this.items.forEach((item) => item.parentExpanded = this.parentExpanded); } handleDefaultSlotChange(event) { const items = slotChangeGetAssignedElements(event).filter((el) => el.matches("calcite-tree-item")); this.items = items; this.updateItems(); } getRootTabIndex() { return !this.child ? 0 : -1; } render() { this.el.ariaMultiSelectable = this.child ? void 0 : toAriaBoolean(this.selectionMode === "multiple" || this.selectionMode === "multichildren"); this.el.role = !this.child ? "tree" : void 0; setAttribute(this.el, "tabIndex", this.getRootTabIndex()); return html`<slot @slotchange=${this.handleDefaultSlotChange}></slot>`; } } customElement("calcite-tree", Tree); export { Tree };