calcite-tree.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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.82
  5. */
  6. import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
  7. import { f as focusElement, n as nodeListToArray } from './dom.js';
  8. import { T as TreeSelectionMode } from './interfaces2.js';
  9. const treeCss = "@-webkit-keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in{0%{opacity:0}100%{opacity:1}}@-webkit-keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-down{0%{opacity:0;-webkit-transform:translate3D(0, -5px, 0);transform:translate3D(0, -5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;-webkit-transform:translate3D(0, 5px, 0);transform:translate3D(0, 5px, 0)}100%{opacity:1;-webkit-transform:translate3D(0, 0, 0);transform:translate3D(0, 0, 0)}}@-webkit-keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}@keyframes in-scale{0%{opacity:0;-webkit-transform:scale3D(0.95, 0.95, 1);transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;-webkit-transform:scale3D(1, 1, 1);transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:var(--calcite-animation-timing);animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{-webkit-animation-name:in;animation-name:in}.calcite-animate__in-down{-webkit-animation-name:in-down;animation-name:in-down}.calcite-animate__in-up{-webkit-animation-name:in-up;animation-name:in-up}.calcite-animate__in-scale{-webkit-animation-name:in-scale;animation-name:in-scale}:root{--calcite-popper-transition:var(--calcite-animation-timing)}:host([hidden]){display:none}:host{display:block;outline:2px solid transparent;outline-offset:2px}";
  10. const Tree = /*@__PURE__*/ proxyCustomElement(class extends HTMLElement {
  11. constructor() {
  12. super();
  13. this.__registerHost();
  14. this.__attachShadow();
  15. this.calciteTreeSelect = createEvent(this, "calciteTreeSelect", 7);
  16. //--------------------------------------------------------------------------
  17. //
  18. // Properties
  19. //
  20. //--------------------------------------------------------------------------
  21. /** Display indentation guide lines. */
  22. this.lines = false;
  23. /** Display input
  24. * @deprecated Use "ancestors" selection-mode for checkbox input.
  25. */
  26. this.inputEnabled = false;
  27. /** Specify the scale of the tree. */
  28. this.scale = "m";
  29. /** Customize how tree selection works.
  30. * @default "single"
  31. * @see [TreeSelectionMode](https://github.com/Esri/calcite-components/blob/master/src/components/tree/interfaces.ts#L5)
  32. */
  33. this.selectionMode = TreeSelectionMode.Single;
  34. }
  35. //--------------------------------------------------------------------------
  36. //
  37. // Lifecycle
  38. //
  39. //--------------------------------------------------------------------------
  40. componentWillRender() {
  41. const parent = this.el.parentElement.closest("calcite-tree");
  42. this.lines = parent ? parent.lines : this.lines;
  43. this.scale = parent ? parent.scale : this.scale;
  44. this.selectionMode = parent ? parent.selectionMode : this.selectionMode;
  45. this.child = !!parent;
  46. }
  47. render() {
  48. return (h(Host, { "aria-multiselectable": this.child
  49. ? undefined
  50. : (this.selectionMode === TreeSelectionMode.Multi ||
  51. this.selectionMode === TreeSelectionMode.MultiChildren).toString(), role: !this.child ? "tree" : undefined, tabIndex: this.getRootTabIndex() }, h("slot", null)));
  52. }
  53. //--------------------------------------------------------------------------
  54. //
  55. // Event Listeners
  56. //
  57. //--------------------------------------------------------------------------
  58. onFocus() {
  59. if (!this.child) {
  60. const focusTarget = this.el.querySelector("calcite-tree-item[selected]") ||
  61. this.el.querySelector("calcite-tree-item");
  62. focusElement(focusTarget);
  63. }
  64. }
  65. onFocusIn(event) {
  66. const focusedFromRootOrOutsideTree = event.relatedTarget === this.el || !this.el.contains(event.relatedTarget);
  67. if (focusedFromRootOrOutsideTree) {
  68. // gives user the ability to tab into external elements (modifying tabindex property will not work in firefox)
  69. this.el.removeAttribute("tabindex");
  70. }
  71. }
  72. onFocusOut(event) {
  73. const willFocusOutsideTree = !this.el.contains(event.relatedTarget);
  74. if (willFocusOutsideTree) {
  75. this.el.tabIndex = this.getRootTabIndex();
  76. }
  77. }
  78. onClick(e) {
  79. const target = e.target;
  80. const childItems = nodeListToArray(target.querySelectorAll("calcite-tree-item"));
  81. if (this.child) {
  82. return;
  83. }
  84. if (!this.child) {
  85. e.preventDefault();
  86. e.stopPropagation();
  87. }
  88. if (this.selectionMode === TreeSelectionMode.Ancestors && !this.child) {
  89. this.updateAncestorTree(e);
  90. return;
  91. }
  92. const shouldSelect = this.selectionMode !== null &&
  93. (!target.hasChildren ||
  94. (target.hasChildren &&
  95. (this.selectionMode === TreeSelectionMode.Children ||
  96. this.selectionMode === TreeSelectionMode.MultiChildren)));
  97. const shouldModifyToCurrentSelection = e.detail.modifyCurrentSelection &&
  98. (this.selectionMode === TreeSelectionMode.Multi ||
  99. this.selectionMode === TreeSelectionMode.MultiChildren);
  100. const shouldSelectChildren = this.selectionMode === TreeSelectionMode.MultiChildren ||
  101. this.selectionMode === TreeSelectionMode.Children;
  102. const shouldClearCurrentSelection = !shouldModifyToCurrentSelection &&
  103. (((this.selectionMode === TreeSelectionMode.Single ||
  104. this.selectionMode === TreeSelectionMode.Multi) &&
  105. childItems.length <= 0) ||
  106. this.selectionMode === TreeSelectionMode.Children ||
  107. this.selectionMode === TreeSelectionMode.MultiChildren);
  108. const shouldExpandTarget = this.selectionMode === TreeSelectionMode.Children ||
  109. this.selectionMode === TreeSelectionMode.MultiChildren;
  110. if (!this.child) {
  111. const targetItems = [];
  112. if (shouldSelect) {
  113. targetItems.push(target);
  114. }
  115. if (shouldSelectChildren) {
  116. childItems.forEach((treeItem) => {
  117. targetItems.push(treeItem);
  118. });
  119. }
  120. if (shouldClearCurrentSelection) {
  121. const selectedItems = nodeListToArray(this.el.querySelectorAll("calcite-tree-item[selected]"));
  122. selectedItems.forEach((treeItem) => {
  123. if (!targetItems.includes(treeItem)) {
  124. treeItem.selected = false;
  125. }
  126. });
  127. }
  128. if (shouldExpandTarget && !e.detail.forceToggle) {
  129. target.expanded = true;
  130. }
  131. if (shouldModifyToCurrentSelection) {
  132. window.getSelection().removeAllRanges();
  133. }
  134. if ((shouldModifyToCurrentSelection && target.selected) ||
  135. (shouldSelectChildren && e.detail.forceToggle)) {
  136. targetItems.forEach((treeItem) => {
  137. treeItem.selected = false;
  138. });
  139. }
  140. else {
  141. targetItems.forEach((treeItem) => {
  142. treeItem.selected = true;
  143. });
  144. }
  145. }
  146. this.calciteTreeSelect.emit({
  147. selected: nodeListToArray(this.el.querySelectorAll("calcite-tree-item")).filter((i) => i.selected)
  148. });
  149. }
  150. keyDownHandler(event) {
  151. var _a;
  152. const root = this.el.closest("calcite-tree:not([child])");
  153. const target = event.target;
  154. if (root === this.el && target.tagName === "CALCITE-TREE-ITEM" && this.el.contains(target)) {
  155. switch (event.key) {
  156. case "ArrowDown":
  157. const next = target.nextElementSibling;
  158. if (next && next.matches("calcite-tree-item")) {
  159. next.focus();
  160. event.preventDefault();
  161. }
  162. break;
  163. case "ArrowLeft":
  164. // When focus is on an open node, closes the node.
  165. if (target.hasChildren && target.expanded) {
  166. target.expanded = false;
  167. event.preventDefault();
  168. break;
  169. }
  170. // When focus is on a child node that is also either an end node or a closed node, moves focus to its parent node.
  171. const parentItem = target.parentElement.closest("calcite-tree-item");
  172. if (parentItem && (!target.hasChildren || target.expanded === false)) {
  173. parentItem.focus();
  174. event.preventDefault();
  175. break;
  176. }
  177. // When focus is on a root node that is also either an end node or a closed node, does nothing.
  178. break;
  179. case "ArrowRight":
  180. if (!target.hasChildren) {
  181. break;
  182. }
  183. if (target.expanded && document.activeElement === target) {
  184. // When focus is on an open node, moves focus to the first child node.
  185. (_a = target.querySelector("calcite-tree-item")) === null || _a === void 0 ? void 0 : _a.focus();
  186. event.preventDefault();
  187. }
  188. else {
  189. // When focus is on a closed node, opens the node; focus does not move.
  190. target.expanded = true;
  191. event.preventDefault();
  192. }
  193. // When focus is on an end node, does nothing.
  194. break;
  195. case "ArrowUp":
  196. const previous = target.previousElementSibling;
  197. if (previous && previous.matches("calcite-tree-item")) {
  198. previous.focus();
  199. event.preventDefault();
  200. }
  201. break;
  202. }
  203. }
  204. }
  205. updateAncestorTree(e) {
  206. const item = e.target;
  207. const children = item.querySelectorAll("calcite-tree-item");
  208. const ancestors = [];
  209. let parent = item.parentElement.closest("calcite-tree-item");
  210. while (parent) {
  211. ancestors.push(parent);
  212. parent = parent.parentElement.closest("calcite-tree-item");
  213. }
  214. item.selected = !item.selected;
  215. item.indeterminate = false;
  216. if (children === null || children === void 0 ? void 0 : children.length) {
  217. children.forEach((el) => {
  218. el.selected = item.selected;
  219. el.indeterminate = false;
  220. });
  221. }
  222. if (ancestors) {
  223. ancestors.forEach((ancestor) => {
  224. const descendants = nodeListToArray(ancestor.querySelectorAll("calcite-tree-item"));
  225. const activeDescendants = descendants.filter((el) => el.selected);
  226. if (activeDescendants.length === 0) {
  227. ancestor.selected = false;
  228. ancestor.indeterminate = false;
  229. return;
  230. }
  231. const indeterminate = activeDescendants.length < descendants.length;
  232. ancestor.indeterminate = indeterminate;
  233. ancestor.selected = !indeterminate;
  234. });
  235. }
  236. this.calciteTreeSelect.emit({
  237. selected: nodeListToArray(this.el.querySelectorAll("calcite-tree-item")).filter((i) => i.selected)
  238. });
  239. }
  240. // --------------------------------------------------------------------------
  241. //
  242. // Private Methods
  243. //
  244. //--------------------------------------------------------------------------
  245. getRootTabIndex() {
  246. return !this.child ? 0 : -1;
  247. }
  248. get el() { return this; }
  249. static get style() { return treeCss; }
  250. }, [1, "calcite-tree", {
  251. "lines": [1540],
  252. "inputEnabled": [4, "input-enabled"],
  253. "child": [1540],
  254. "scale": [1537],
  255. "selectionMode": [1537, "selection-mode"]
  256. }, [[0, "focus", "onFocus"], [0, "focusin", "onFocusIn"], [0, "focusout", "onFocusOut"], [0, "calciteTreeItemSelect", "onClick"], [0, "keydown", "keyDownHandler"]]]);
  257. function defineCustomElement$1() {
  258. if (typeof customElements === "undefined") {
  259. return;
  260. }
  261. const components = ["calcite-tree"];
  262. components.forEach(tagName => { switch (tagName) {
  263. case "calcite-tree":
  264. if (!customElements.get(tagName)) {
  265. customElements.define(tagName, Tree);
  266. }
  267. break;
  268. } });
  269. }
  270. defineCustomElement$1();
  271. const CalciteTree = Tree;
  272. const defineCustomElement = defineCustomElement$1;
  273. export { CalciteTree, defineCustomElement };