tree-item.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.97
  5. */
  6. import { Host, h } from "@stencil/core";
  7. import { TreeSelectionMode } from "../tree/interfaces";
  8. import { nodeListToArray, getElementDir, filterDirectChildren, getSlotted, toAriaBoolean } from "../../utils/dom";
  9. import { CSS, SLOTS, ICONS } from "./resources";
  10. import { CSS_UTILITY } from "../../utils/resources";
  11. import { connectConditionalSlotComponent, disconnectConditionalSlotComponent } from "../../utils/conditionalSlot";
  12. import { updateHostInteraction } from "../../utils/interactive";
  13. /**
  14. * @slot - A slot for adding the component's content.
  15. * @slot children - A slot for adding nested `calcite-tree` elements.
  16. */
  17. export class TreeItem {
  18. constructor() {
  19. //--------------------------------------------------------------------------
  20. //
  21. // Properties
  22. //
  23. //--------------------------------------------------------------------------
  24. /**
  25. * When `true`, interaction is prevented and the component is displayed with lower opacity.
  26. */
  27. this.disabled = false;
  28. /** When `true`, the component is selected. */
  29. this.selected = false;
  30. /** When `true`, the component is expanded. */
  31. this.expanded = false;
  32. /**
  33. * @internal
  34. */
  35. this.parentExpanded = false;
  36. /**
  37. * @internal
  38. */
  39. this.depth = -1;
  40. /**
  41. * @internal
  42. */
  43. this.hasChildren = null;
  44. this.iconClickHandler = (event) => {
  45. event.stopPropagation();
  46. this.expanded = !this.expanded;
  47. };
  48. this.childrenClickHandler = (event) => event.stopPropagation();
  49. //--------------------------------------------------------------------------
  50. //
  51. // Private Methods
  52. //
  53. //--------------------------------------------------------------------------
  54. this.updateParentIsExpanded = (el, expanded) => {
  55. const items = getSlotted(el, SLOTS.children, {
  56. all: true,
  57. selector: "calcite-tree-item"
  58. });
  59. items.forEach((item) => (item.parentExpanded = expanded));
  60. };
  61. this.updateAncestorTree = () => {
  62. var _a;
  63. if (this.selected && this.selectionMode === TreeSelectionMode.Ancestors) {
  64. const ancestors = [];
  65. let parent = this.parentTreeItem;
  66. while (parent) {
  67. ancestors.push(parent);
  68. parent = (_a = parent.parentElement) === null || _a === void 0 ? void 0 : _a.closest("calcite-tree-item");
  69. }
  70. ancestors.forEach((item) => (item.indeterminate = true));
  71. return;
  72. }
  73. };
  74. }
  75. expandedHandler(newValue) {
  76. this.updateParentIsExpanded(this.el, newValue);
  77. }
  78. getselectionMode() {
  79. this.isSelectionMultiLike =
  80. this.selectionMode === TreeSelectionMode.Multi ||
  81. this.selectionMode === TreeSelectionMode.MultiChildren;
  82. }
  83. //--------------------------------------------------------------------------
  84. //
  85. // Lifecycle
  86. //
  87. //--------------------------------------------------------------------------
  88. connectedCallback() {
  89. var _a;
  90. this.parentTreeItem = (_a = this.el.parentElement) === null || _a === void 0 ? void 0 : _a.closest("calcite-tree-item");
  91. if (this.parentTreeItem) {
  92. const { expanded } = this.parentTreeItem;
  93. this.updateParentIsExpanded(this.parentTreeItem, expanded);
  94. }
  95. connectConditionalSlotComponent(this);
  96. }
  97. disconnectedCallback() {
  98. disconnectConditionalSlotComponent(this);
  99. }
  100. componentWillRender() {
  101. var _a;
  102. this.hasChildren = !!this.el.querySelector("calcite-tree");
  103. this.depth = 0;
  104. let parentTree = this.el.closest("calcite-tree");
  105. if (!parentTree) {
  106. return;
  107. }
  108. this.selectionMode = parentTree.selectionMode;
  109. this.scale = parentTree.scale || "m";
  110. this.lines = parentTree.lines;
  111. let nextParentTree;
  112. while (parentTree) {
  113. nextParentTree = (_a = parentTree.parentElement) === null || _a === void 0 ? void 0 : _a.closest("calcite-tree");
  114. if (nextParentTree === parentTree) {
  115. break;
  116. }
  117. else {
  118. parentTree = nextParentTree;
  119. this.depth = this.depth + 1;
  120. }
  121. }
  122. }
  123. componentDidLoad() {
  124. this.updateAncestorTree();
  125. }
  126. componentDidRender() {
  127. updateHostInteraction(this, () => this.parentExpanded || this.depth === 1);
  128. }
  129. render() {
  130. const rtl = getElementDir(this.el) === "rtl";
  131. const showBulletPoint = this.selectionMode === TreeSelectionMode.Single ||
  132. this.selectionMode === TreeSelectionMode.Children;
  133. const showCheckmark = this.selectionMode === TreeSelectionMode.Multi ||
  134. this.selectionMode === TreeSelectionMode.MultiChildren;
  135. const showBlank = this.selectionMode === TreeSelectionMode.None && !this.hasChildren;
  136. const chevron = this.hasChildren ? (h("calcite-icon", { class: {
  137. [CSS.chevron]: true,
  138. [CSS_UTILITY.rtl]: rtl
  139. }, "data-test-id": "icon", icon: ICONS.chevronRight, onClick: this.iconClickHandler, scale: "s" })) : null;
  140. const defaultSlotNode = h("slot", { key: "default-slot" });
  141. const checkbox = this.selectionMode === TreeSelectionMode.Ancestors ? (h("label", { class: CSS.checkboxLabel, key: "checkbox-label" }, h("calcite-checkbox", { checked: this.selected, class: CSS.checkbox, "data-test-id": "checkbox", indeterminate: this.hasChildren && this.indeterminate, scale: this.scale, tabIndex: -1 }), defaultSlotNode)) : null;
  142. const selectedIcon = showBulletPoint
  143. ? ICONS.bulletPoint
  144. : showCheckmark
  145. ? ICONS.checkmark
  146. : showBlank
  147. ? ICONS.blank
  148. : null;
  149. const itemIndicator = selectedIcon ? (h("calcite-icon", { class: {
  150. [CSS.bulletPointIcon]: selectedIcon === ICONS.bulletPoint,
  151. [CSS.checkmarkIcon]: selectedIcon === ICONS.checkmark,
  152. [CSS_UTILITY.rtl]: rtl
  153. }, icon: selectedIcon, scale: "s" })) : null;
  154. const hidden = !(this.parentExpanded || this.depth === 1);
  155. return (h(Host, { "aria-expanded": this.hasChildren ? toAriaBoolean(this.expanded) : undefined, "aria-hidden": toAriaBoolean(hidden), "aria-selected": this.selected ? "true" : showCheckmark ? "false" : undefined, "calcite-hydrated-hidden": hidden, role: "treeitem" }, h("div", { class: {
  156. [CSS.nodeContainer]: true,
  157. [CSS_UTILITY.rtl]: rtl
  158. }, "data-selection-mode": this.selectionMode, ref: (el) => (this.defaultSlotWrapper = el) }, chevron, itemIndicator, checkbox ? checkbox : defaultSlotNode), h("div", { class: {
  159. [CSS.childrenContainer]: true,
  160. [CSS_UTILITY.rtl]: rtl
  161. }, "data-test-id": "calcite-tree-children", onClick: this.childrenClickHandler, ref: (el) => (this.childrenSlotWrapper = el), role: this.hasChildren ? "group" : undefined }, h("slot", { name: SLOTS.children }))));
  162. }
  163. //--------------------------------------------------------------------------
  164. //
  165. // Event Listeners
  166. //
  167. //--------------------------------------------------------------------------
  168. onClick(event) {
  169. // Solve for if the item is clicked somewhere outside the slotted anchor.
  170. // Anchor is triggered anywhere you click
  171. const [link] = filterDirectChildren(this.el, "a");
  172. if (link && event.composedPath()[0].tagName.toLowerCase() !== "a") {
  173. const target = link.target === "" ? "_self" : link.target;
  174. window.open(link.href, target);
  175. }
  176. this.calciteInternalTreeItemSelect.emit({
  177. modifyCurrentSelection: this.selectionMode === TreeSelectionMode.Ancestors || this.isSelectionMultiLike,
  178. forceToggle: false
  179. });
  180. }
  181. keyDownHandler(event) {
  182. let root;
  183. switch (event.key) {
  184. case " ":
  185. this.calciteInternalTreeItemSelect.emit({
  186. modifyCurrentSelection: this.isSelectionMultiLike,
  187. forceToggle: false
  188. });
  189. event.preventDefault();
  190. break;
  191. case "Enter":
  192. // activates a node, i.e., performs its default action. For parent nodes, one possible default action is to open or close the node. In single-select trees where selection does not follow focus (see note below), the default action is typically to select the focused node.
  193. const link = nodeListToArray(this.el.children).find((el) => el.matches("a"));
  194. if (link) {
  195. link.click();
  196. this.selected = true;
  197. }
  198. else {
  199. this.calciteInternalTreeItemSelect.emit({
  200. modifyCurrentSelection: this.isSelectionMultiLike,
  201. forceToggle: false
  202. });
  203. }
  204. event.preventDefault();
  205. break;
  206. case "Home":
  207. root = this.el.closest("calcite-tree:not([child])");
  208. const firstNode = root.querySelector("calcite-tree-item");
  209. firstNode === null || firstNode === void 0 ? void 0 : firstNode.focus();
  210. break;
  211. case "End":
  212. root = this.el.closest("calcite-tree:not([child])");
  213. let currentNode = root.children[root.children.length - 1]; // last child
  214. let currentTree = nodeListToArray(currentNode.children).find((el) => el.matches("calcite-tree"));
  215. while (currentTree) {
  216. currentNode = currentTree.children[root.children.length - 1];
  217. currentTree = nodeListToArray(currentNode.children).find((el) => el.matches("calcite-tree"));
  218. }
  219. currentNode === null || currentNode === void 0 ? void 0 : currentNode.focus();
  220. break;
  221. }
  222. }
  223. static get is() { return "calcite-tree-item"; }
  224. static get encapsulation() { return "shadow"; }
  225. static get originalStyleUrls() {
  226. return {
  227. "$": ["tree-item.scss"]
  228. };
  229. }
  230. static get styleUrls() {
  231. return {
  232. "$": ["tree-item.css"]
  233. };
  234. }
  235. static get properties() {
  236. return {
  237. "disabled": {
  238. "type": "boolean",
  239. "mutable": false,
  240. "complexType": {
  241. "original": "boolean",
  242. "resolved": "boolean",
  243. "references": {}
  244. },
  245. "required": false,
  246. "optional": false,
  247. "docs": {
  248. "tags": [],
  249. "text": "When `true`, interaction is prevented and the component is displayed with lower opacity."
  250. },
  251. "attribute": "disabled",
  252. "reflect": true,
  253. "defaultValue": "false"
  254. },
  255. "selected": {
  256. "type": "boolean",
  257. "mutable": true,
  258. "complexType": {
  259. "original": "boolean",
  260. "resolved": "boolean",
  261. "references": {}
  262. },
  263. "required": false,
  264. "optional": false,
  265. "docs": {
  266. "tags": [],
  267. "text": "When `true`, the component is selected."
  268. },
  269. "attribute": "selected",
  270. "reflect": true,
  271. "defaultValue": "false"
  272. },
  273. "expanded": {
  274. "type": "boolean",
  275. "mutable": true,
  276. "complexType": {
  277. "original": "boolean",
  278. "resolved": "boolean",
  279. "references": {}
  280. },
  281. "required": false,
  282. "optional": false,
  283. "docs": {
  284. "tags": [],
  285. "text": "When `true`, the component is expanded."
  286. },
  287. "attribute": "expanded",
  288. "reflect": true,
  289. "defaultValue": "false"
  290. },
  291. "parentExpanded": {
  292. "type": "boolean",
  293. "mutable": false,
  294. "complexType": {
  295. "original": "boolean",
  296. "resolved": "boolean",
  297. "references": {}
  298. },
  299. "required": false,
  300. "optional": false,
  301. "docs": {
  302. "tags": [{
  303. "name": "internal",
  304. "text": undefined
  305. }],
  306. "text": ""
  307. },
  308. "attribute": "parent-expanded",
  309. "reflect": false,
  310. "defaultValue": "false"
  311. },
  312. "depth": {
  313. "type": "number",
  314. "mutable": true,
  315. "complexType": {
  316. "original": "number",
  317. "resolved": "number",
  318. "references": {}
  319. },
  320. "required": false,
  321. "optional": false,
  322. "docs": {
  323. "tags": [{
  324. "name": "internal",
  325. "text": undefined
  326. }],
  327. "text": ""
  328. },
  329. "attribute": "depth",
  330. "reflect": true,
  331. "defaultValue": "-1"
  332. },
  333. "hasChildren": {
  334. "type": "boolean",
  335. "mutable": true,
  336. "complexType": {
  337. "original": "boolean",
  338. "resolved": "boolean",
  339. "references": {}
  340. },
  341. "required": false,
  342. "optional": false,
  343. "docs": {
  344. "tags": [{
  345. "name": "internal",
  346. "text": undefined
  347. }],
  348. "text": ""
  349. },
  350. "attribute": "has-children",
  351. "reflect": true,
  352. "defaultValue": "null"
  353. },
  354. "lines": {
  355. "type": "boolean",
  356. "mutable": true,
  357. "complexType": {
  358. "original": "boolean",
  359. "resolved": "boolean",
  360. "references": {}
  361. },
  362. "required": false,
  363. "optional": false,
  364. "docs": {
  365. "tags": [{
  366. "name": "internal",
  367. "text": undefined
  368. }],
  369. "text": ""
  370. },
  371. "attribute": "lines",
  372. "reflect": true
  373. },
  374. "inputEnabled": {
  375. "type": "boolean",
  376. "mutable": false,
  377. "complexType": {
  378. "original": "boolean",
  379. "resolved": "boolean",
  380. "references": {}
  381. },
  382. "required": false,
  383. "optional": false,
  384. "docs": {
  385. "tags": [{
  386. "name": "internal",
  387. "text": undefined
  388. }, {
  389. "name": "deprecated",
  390. "text": "Use `selectionMode=\"ancestors\"` for checkbox input."
  391. }],
  392. "text": "Displays checkboxes (set on parent)."
  393. },
  394. "attribute": "input-enabled",
  395. "reflect": false
  396. },
  397. "scale": {
  398. "type": "string",
  399. "mutable": true,
  400. "complexType": {
  401. "original": "Scale",
  402. "resolved": "\"l\" | \"m\" | \"s\"",
  403. "references": {
  404. "Scale": {
  405. "location": "import",
  406. "path": "../interfaces"
  407. }
  408. }
  409. },
  410. "required": false,
  411. "optional": false,
  412. "docs": {
  413. "tags": [{
  414. "name": "internal",
  415. "text": undefined
  416. }],
  417. "text": ""
  418. },
  419. "attribute": "scale",
  420. "reflect": true
  421. },
  422. "indeterminate": {
  423. "type": "boolean",
  424. "mutable": false,
  425. "complexType": {
  426. "original": "boolean",
  427. "resolved": "boolean",
  428. "references": {}
  429. },
  430. "required": false,
  431. "optional": false,
  432. "docs": {
  433. "tags": [{
  434. "name": "internal",
  435. "text": undefined
  436. }],
  437. "text": "In ancestor selection mode, show as indeterminate when only some children are selected."
  438. },
  439. "attribute": "indeterminate",
  440. "reflect": true
  441. },
  442. "selectionMode": {
  443. "type": "string",
  444. "mutable": true,
  445. "complexType": {
  446. "original": "TreeSelectionMode",
  447. "resolved": "TreeSelectionMode.Ancestors | TreeSelectionMode.Children | TreeSelectionMode.Multi | TreeSelectionMode.MultiChildren | TreeSelectionMode.None | TreeSelectionMode.Single",
  448. "references": {
  449. "TreeSelectionMode": {
  450. "location": "import",
  451. "path": "../tree/interfaces"
  452. }
  453. }
  454. },
  455. "required": false,
  456. "optional": false,
  457. "docs": {
  458. "tags": [{
  459. "name": "internal",
  460. "text": undefined
  461. }],
  462. "text": ""
  463. },
  464. "attribute": "selection-mode",
  465. "reflect": true
  466. }
  467. };
  468. }
  469. static get events() {
  470. return [{
  471. "method": "calciteInternalTreeItemSelect",
  472. "name": "calciteInternalTreeItemSelect",
  473. "bubbles": true,
  474. "cancelable": false,
  475. "composed": true,
  476. "docs": {
  477. "tags": [{
  478. "name": "internal",
  479. "text": undefined
  480. }],
  481. "text": ""
  482. },
  483. "complexType": {
  484. "original": "TreeItemSelectDetail",
  485. "resolved": "TreeItemSelectDetail",
  486. "references": {
  487. "TreeItemSelectDetail": {
  488. "location": "import",
  489. "path": "./interfaces"
  490. }
  491. }
  492. }
  493. }];
  494. }
  495. static get elementRef() { return "el"; }
  496. static get watchers() {
  497. return [{
  498. "propName": "expanded",
  499. "methodName": "expandedHandler"
  500. }, {
  501. "propName": "selectionMode",
  502. "methodName": "getselectionMode"
  503. }];
  504. }
  505. static get listeners() {
  506. return [{
  507. "name": "click",
  508. "method": "onClick",
  509. "target": undefined,
  510. "capture": false,
  511. "passive": false
  512. }, {
  513. "name": "keydown",
  514. "method": "keyDownHandler",
  515. "target": undefined,
  516. "capture": false,
  517. "passive": false
  518. }];
  519. }
  520. }