• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import './LitTreeNode';
17import { BaseElement, element } from '../BaseElement';
18import { type LitTreeNode } from './LitTreeNode';
19
20export interface TreeItemData   {
21  key: string;
22  title: string;
23  icon?: string; //节点的自定义图标  设置show-icon才会生效
24  selected?: boolean; //控制是否选择该节点
25  checked?: boolean;
26  disable?: boolean; //控制是否显示checkbox
27  children?: Array<TreeItemData> | null | undefined;
28}
29
30@element('lit-tree')
31export class LitTree extends BaseElement {
32  private _treeData: Array<TreeItemData> = [];
33  private currentSelectedNode: any;
34  private currentSelectedData: any;
35  private proxyData: any;
36  private nodeList: Array<LitTreeNode> = [];
37  private contextMenu: HTMLDivElement | null | undefined;
38  private srcDragElement: any;
39  private dragDirection: string | null | undefined;
40
41  static get observedAttributes(): string[] {
42    return ['show-line', 'show-icon', 'checkable', 'foldable', 'dragable', 'multiple']; //foldable 表示点击目录是否可以折叠
43  }
44
45  set treeData(value: Array<TreeItemData>) {
46    this._treeData = value;
47    this.shadowRoot!.querySelector('#root')!.innerHTML = '';
48    this.nodeList = [];
49    this.drawTree(this.shadowRoot!.querySelector('#root'), value, true);
50
51    /*双向绑定*/
52    const handler = {
53      get: (target: any, propkey: any): any => {
54        return target[propkey];
55      },
56      set: (target: any, propkey: any, value: any, receiver: any): boolean => {
57        if (target[propkey] !== value) {
58          if (!value.children) {
59            value.children = new Proxy([], handler);
60          } else {
61            value.children = new Proxy(value.children, handler);
62          }
63          target[propkey] = value;
64          if (!this.currentSelectedNode) {
65            this._insertNode(null, value);
66          } else {
67            if (this.currentSelectedNode.nextElementSibling) {
68              this._insertNode(this.currentSelectedNode.nextElementSibling, value);
69            } else {
70              this.currentSelectedNode.setAttribute('show-arrow', 'true');
71              let ul = document.createElement('ul');
72              // @ts-ignore
73              ul.open = 'true';
74              ul.style.transition = '.3s all';
75              this.currentSelectedNode.parentElement.append(ul);
76              this.currentSelectedNode.arrow = true;
77              this._insertNode(ul, value);
78            }
79          }
80        }
81        return true;
82      },
83    };
84    let setProxy = (v: Array<TreeItemData>): void => {
85      v.forEach((a) => {
86        if (!a.children) {
87          a.children = new Proxy([], handler);
88        } else {
89          a.children = new Proxy(a.children, handler);
90          setProxy(a.children || []);
91        }
92      });
93    };
94    setProxy(this._treeData);
95    this.proxyData = new Proxy(this._treeData, handler);
96  }
97
98  get multiple(): boolean {
99    return this.hasAttribute('multiple');
100  }
101
102  set multiple(value: boolean) {
103    if (value) {
104      this.setAttribute('multiple', '');
105    } else {
106      this.removeAttribute('multiple');
107    }
108  }
109
110  get treeData(): TreeItemData[] {
111    return this.proxyData;
112  }
113
114  set onSelect(fn: any) {
115    this.addEventListener('onSelect', fn);
116  }
117
118  set onChange(fn: any) {
119    this.addEventListener('onChange', fn);
120  }
121
122  set foldable(value: any) {
123    if (value) {
124      this.setAttribute('foldable', '');
125    } else {
126      this.removeAttribute('foldable');
127    }
128  }
129
130  //当 custom element首次被插入文档DOM时,被调用。
131  connectedCallback(): void {
132    this.onclick = (ev): void => {
133      this.contextMenu!.style.display = 'none';
134      this.currentSelectedData = null;
135      this.currentSelectedNode = null;
136      this.selectedNode(null);
137    };
138  }
139
140  getCheckdKeys(): any[] {
141    return Array.from(this.shadowRoot!.querySelectorAll('lit-tree-node[checked]')).map((a: any) => a.data.key);
142  }
143
144  getCheckdNodes(): any[] {
145    return Array.from(this.shadowRoot!.querySelectorAll('lit-tree-node[checked]')).map((a: any) => a.data);
146  }
147
148  //展开所有节点
149  expandKeys(keys: Array<string>): void {
150    keys.forEach((k) => this.shadowRoot!.querySelectorAll(`lit-tree-node[key='${k}']`).forEach((b: any) => b.expand()));
151  }
152
153  //收起所有节点
154  collapseKeys(keys: Array<string>): void {
155    keys.forEach((k) =>
156      this.shadowRoot!.querySelectorAll(`lit-tree-node[key='${k}']`).forEach((b: any) => b.collapse())
157    );
158  }
159
160  checkedKeys(keys: Array<string>): void {
161    keys.forEach((k) =>
162      this.shadowRoot!.querySelectorAll(`lit-tree-node[key='${k}']`).forEach((b: any) => {
163        b.setAttribute('checked', 'true');
164        b.checkHandler();
165      })
166    );
167  }
168
169  uncheckedKeys(keys: Array<string>): void {
170    keys.forEach((k) =>
171      this.shadowRoot!.querySelectorAll(`lit-tree-node[key='${k}']`).forEach((b: any) => {
172        b.removeAttribute('checked');
173        b.removeAttribute('missing');
174        b.checkHandler();
175      })
176    );
177  }
178
179  drawTree(parent: any, array: Array<TreeItemData>, topDepth: boolean = false): void {
180    array.forEach((a:TreeItemData) => {
181      let li: HTMLLIElement = document.createElement('li');
182      let node: LitTreeNode = document.createElement('lit-tree-node') as LitTreeNode;
183      node.title = a.title;
184      node.setAttribute('key', a.key);
185      node.topDepth = topDepth;
186      this.treeNodeDragable(node,a);
187      // @ts-ignore
188      li.data = a;
189      li.append(node);
190      parent.append(li);
191      let ul: HTMLUListElement = document.createElement('ul');
192      // @ts-ignore
193      ul.open = 'true';
194      ul.style.transition = '.3s all';
195      this.addEvent(a,node,li,ul);
196      // node 添加右键菜单功能
197      node.oncontextmenu = (ev): void => {
198        ev.preventDefault();
199        this.selectedNode(node);
200        this.currentSelectedNode = node;
201        this.currentSelectedData = node.data;
202        this.contextMenu!.style.display = 'block';
203        this.contextMenu!.style.left = ev.pageX + 'px';
204        this.contextMenu!.style.top = ev.pageY + 'px';
205      };
206    });
207    this.oncontextmenu = (ev): void => {
208      ev.preventDefault();
209      this.contextMenu!.style.display = 'block';
210      this.contextMenu!.style.left = ev.pageX + 'px';
211      this.contextMenu!.style.top = ev.pageY + 'px';
212    };
213  }
214
215  treeNodeDragable(node: LitTreeNode,a:TreeItemData):void{
216    let that = this;
217    if (this.hasAttribute('dragable')) {
218      node.draggable = true;
219      document.ondragover = function (e) {
220        e.preventDefault();
221      };
222      //在拖动目标上触发事件 (源元素)
223      node.ondrag = (ev) => this.onDrag(ev); //元素正在拖动时触发
224      node.ondragstart = (ev) => this.onDragStart(ev); //用户开始拖动元素时触发
225      node.ondragend = (ev) => this.onDragEnd(ev); // 用户完成元素拖动后触发
226      //释放目标时触发的事件:
227      node.ondragenter = (ev) => this.onDragEnter(ev); //当被鼠标拖动的对象进入其容器范围内时触发此事件
228      node.ondragover = (ev) => this.onDragOver(ev); //当某被拖动的对象在另一对象容器范围内拖动时触发此事件
229      node.ondragleave = (ev) => this.onDragLeave(ev); //当被鼠标拖动的对象离开其容器范围内时触发此事件
230      node.ondrop = (ev) => this.onDrop(ev); //在一个拖动过程中,释放鼠标键时触发此事件
231    }
232    node.selected = a.selected || false; //是否选中行
233    node.checked = a.checked || false; // 是否勾选
234    node.data = a;
235    node.addEventListener('change', (e: any): void => {
236      if (e.detail && !this.multiple) {
237        this.nodeList.forEach((item) => {
238          item.checked = item.data!.key === node.data!.key;
239          item.data!.checked = item.checked;
240        });
241      }
242      var litTreeNodes = this.nodeList.filter((it) => it.checked);
243      if (litTreeNodes.length === 0) {
244        node.checked = true;
245        node.data!.checked = true;
246      }
247      that.dispatchEvent(new CustomEvent('onChange', { detail: { data: (node as any).data, checked: e.detail } }));
248    });
249    node.multiple = this.hasAttribute('multiple');
250    node.checkable = this.getAttribute('checkable') || 'false';
251    this.nodeList.push(node);
252  }
253
254  addEvent(a:TreeItemData,node: LitTreeNode,li: HTMLLIElement,ul: HTMLUListElement):void{
255    if (a.children && a.children.length > 0) {
256      if (this.hasAttribute('show-icon')) {
257        if (a.icon) {
258          (node as any).iconName = a.icon;
259        } else {
260          (node as any).iconName = 'folder';
261        }
262      } else {
263        node.iconName = '';
264      }
265      node.arrow = true;
266      li.append(ul);
267      this.drawTree(ul, a.children);
268    } else {
269      if (this.hasAttribute('show-icon')) {
270        if (a.icon) {
271          node.iconName = a.icon;
272        } else {
273          node.iconName = 'file';
274        }
275      } else {
276        node.iconName = '';
277      }
278      node.arrow = false;
279    }
280    li.onclick = (e): void => {
281      e.stopPropagation();
282      if (this.hasAttribute('foldable')) {
283        // @ts-ignore
284        if (li.data.children && li.data.children.length > 0) {
285          node.autoExpand();
286        } else {
287          // @ts-ignore
288          this.dispatchEvent(new CustomEvent('onSelect', { detail: li.data }));
289          this.selectedNode(node);
290        }
291      } else {
292        // @ts-ignore
293        this.dispatchEvent(new CustomEvent('onSelect', { detail: li.data }));
294        this.selectedNode(node);
295      }
296    };
297  }
298
299  //取消所有节点的选中状态 然后选中当前node节点
300  selectedNode(node: LitTreeNode | null | undefined): void {
301    this.shadowRoot!.querySelectorAll<LitTreeNode>('lit-tree-node').forEach((a) => {
302      a.selected = false;
303    });
304    if (node) {
305      node.selected = true;
306    }
307  }
308
309  closeContextMenu(): void {
310    this.contextMenu!.style.display = 'none';
311  }
312
313  onDrag(e: MouseEvent): void {}
314
315  onDragStart(ev: MouseEvent): undefined {
316    this.srcDragElement = ev.target;
317    (ev.target! as LitTreeNode).open = 'true';
318    (ev.target! as LitTreeNode).autoExpand();
319    return undefined;
320  }
321
322  onDragEnd(ev: MouseEvent): undefined {
323    this.srcDragElement = null;
324    return undefined;
325  }
326
327  onDragEnter(ev: MouseEvent): undefined {
328    (ev.target as LitTreeNode).style.backgroundColor = '#42b98333';
329    return undefined;
330  }
331
332  onDragOver(ev: MouseEvent): undefined {
333    let node = ev.target as LitTreeNode;
334    if (this.srcDragElement.data.key === node.data!.key) {
335      return;
336    }
337    let rect = (ev.currentTarget! as any).getBoundingClientRect();
338    if (ev.clientX >= rect.left + rect.width / 3 && ev.clientX < rect.left + rect.width) {
339      //bottom-right
340      this.dragDirection = 'bottom-right';
341      node.drawLine('bottom-right');
342    } else if (ev.clientY >= rect.top && ev.clientY < rect.top + rect.height / 2) {
343      //上面
344      this.dragDirection = 'top';
345      node.drawLine('top');
346    } else if (ev.clientY <= rect.bottom && ev.clientY > rect.top + rect.height / 2) {
347      //下面
348      this.dragDirection = 'bottom';
349      node.drawLine('bottom');
350    }
351    return undefined;
352  }
353
354  onDragLeave(ev: MouseEvent): undefined {
355    (ev.target as LitTreeNode).style.backgroundColor = '#ffffff00';
356    (ev.target as LitTreeNode).drawLine('');
357    return undefined;
358  }
359
360  onDrop(ev: MouseEvent): undefined {
361    (ev.target as LitTreeNode).style.backgroundColor = '#ffffff00';
362    (ev.target as LitTreeNode).drawLine('');
363    //移动的不是node节点 而是上层的li节点
364    let srcData = this.srcDragElement.data; //获取原节点的data数据
365    let dstData = (ev.target as LitTreeNode).data; //获取目标节点的data数据
366    if (srcData.key === dstData!.key) {
367      return;
368    } //同一个节点不用处理
369    let srcElement = this.srcDragElement.parentElement;
370    let srcParentElement = srcElement.parentElement;
371    let dstElement = (ev.target as LitTreeNode).parentElement;
372    srcElement.parentElement.removeChild(srcElement); //node li ul  从 ul 中移除 li
373    if (this.dragDirection === 'top') {
374      dstElement!.parentElement!.insertBefore(srcElement, dstElement);
375    } else if (this.dragDirection === 'bottom') {
376      dstElement!.parentElement!.insertBefore(srcElement, dstElement!.nextSibling);
377    } else if (this.dragDirection === 'bottom-right') {
378      let ul = dstElement!.querySelector('ul');
379      if (!ul) {
380        ul = document.createElement('ul');
381        ul.style.cssText = 'transition: all 0.3s ease 0s;';
382        dstElement!.appendChild(ul);
383      }
384      dstElement!.querySelector<LitTreeNode>('lit-tree-node')!.arrow = true; //拖动进入子节点,需要开启箭头
385      ul.appendChild(srcElement);
386    }
387    let ul1 = dstElement!.querySelector('ul'); //如果拖动后目标节点的子节点没有记录,需要关闭arrow箭头
388    if (ul1) {
389      if (ul1.childElementCount === 0) {
390        (ul1.previousElementSibling! as LitTreeNode).arrow = false;
391      }
392    }
393    if (srcParentElement.childElementCount === 0) {
394      srcParentElement.previousElementSibling.arrow = false;
395    } //如果拖动的原节点的父节点没有子节点需要 关闭arrow箭头
396    //拖动调整结构后修改 data树形数据结构
397    this.removeDataNode(this._treeData, srcData);
398    this.addDataNode(this._treeData, srcData, dstData!.key, this.dragDirection!);
399    this.dispatchEvent(
400      new CustomEvent('drop', {
401        detail: {
402          treeData: this._treeData,
403          srcData: srcData,
404          dstData: dstData,
405          type: this.dragDirection,
406        },
407      })
408    );
409    ev.stopPropagation();
410    return undefined;
411  }
412
413  //移除treeData中指定的节点 通过key匹配
414  removeDataNode(arr: Array<TreeItemData>, d: TreeItemData): void {
415    let delIndex = arr.findIndex((v) => v.key === d.key);
416    if (delIndex > -1) {
417      arr.splice(delIndex, 1);
418      return;
419    }
420    for (let i = 0; i < arr.length; i++) {
421      if (arr[i].children && arr[i].children!.length > 0) {
422        this.removeDataNode(arr[i].children!, d);
423      }
424    }
425  }
426
427  //中array中匹配到key为k的节点, t='bottom-right' 把d加入到该节点的children中去 t='top' 添加到找到的节点前面 t='bottom' 添加到找到的节点后面
428  addDataNode(arr: Array<TreeItemData>, d: TreeItemData, k: string, t: string): void {
429    for (let i = 0; i < arr.length; i++) {
430      if (arr[i].key === k) {
431        if (t === 'bottom-right') {
432          if (!arr[i].children) {
433            arr[i].children = [];
434          }
435          arr[i].children!.push(d);
436        } else if (t === 'top') {
437          arr.splice(i, 0, d);
438        } else if (t === 'bottom') {
439          arr.splice(i + 1, 0, d);
440        }
441        return;
442      } else {
443        if (arr[i].children) {
444          this.addDataNode(arr[i].children || [], d, k, t);
445        }
446      }
447    }
448  }
449
450  insert(obj: TreeItemData): void {
451    if (this.currentSelectedData) {
452      this.currentSelectedData.children.push(obj);
453    } else {
454      this.treeData.push(obj);
455    }
456  }
457  insertNodeDragEvent(insertNode: LitTreeNode){
458    if (this.hasAttribute('dragable')) {
459      insertNode.draggable = true;
460      document.ondragover = function (e): void {
461        e.preventDefault();
462      };
463      //在拖动目标上触发事件 (源元素)
464      insertNode.ondrag = (ev): void => this.onDrag(ev); //元素正在拖动时触发
465      insertNode.ondragstart = (ev): undefined => this.onDragStart(ev); //用户开始拖动元素时触发
466      insertNode.ondragend = (ev): undefined => this.onDragEnd(ev); // 用户完成元素拖动后触发
467      //释放目标时触发的事件:
468      insertNode.ondragenter = (ev): undefined => this.onDragEnter(ev); //当被鼠标拖动的对象进入其容器范围内时触发此事件
469      insertNode.ondragover = (ev): undefined => this.onDragOver(ev); //当某被拖动的对象在另一对象容器范围内拖动时触发此事件
470      insertNode.ondragleave = (ev): undefined => this.onDragLeave(ev); //当被鼠标拖动的对象离开其容器范围内时触发此事件
471      insertNode.ondrop = (ev): undefined => this.onDrop(ev); //在一个拖动过程中,释放鼠标键时触发此事件
472    }
473  }
474  _insertNode(parent: any, a: any): void {
475    if (!parent) {
476      parent = this.shadowRoot!.querySelector('#root');
477    }
478    let li: HTMLLIElement = document.createElement('li');
479    let insertNode: LitTreeNode = document.createElement('lit-tree-node') as LitTreeNode;
480    insertNode.title = a.title;
481    insertNode.setAttribute('key', a.key);
482    this.setDragableOfEvent(insertNode);
483    if (this.hasAttribute('dragable')) {
484      insertNode.draggable = true;
485      document.ondragover = function (e): void {
486        e.preventDefault();
487      };
488      //在拖动目标上触发事件 (源元素)
489      insertNode.ondrag = (ev): void => this.onDrag(ev); //元素正在拖动时触发
490      insertNode.ondragstart = (ev): undefined => this.onDragStart(ev); //用户开始拖动元素时触发
491      insertNode.ondragend = (ev): undefined => this.onDragEnd(ev); // 用户完成元素拖动后触发
492      //释放目标时触发的事件:
493      insertNode.ondragenter = (ev): undefined => this.onDragEnter(ev); //当被鼠标拖动的对象进入其容器范围内时触发此事件
494      insertNode.ondragover = (ev): undefined => this.onDragOver(ev); //当某被拖动的对象在另一对象容器范围内拖动时触发此事件
495      insertNode.ondragleave = (ev): undefined => this.onDragLeave(ev); //当被鼠标拖动的对象离开其容器范围内时触发此事件
496      insertNode.ondrop = (ev): undefined => this.onDrop(ev); //在一个拖动过程中,释放鼠标键时触发此事件
497    }
498    insertNode.selected = a.selected || false; //是否选中行
499    insertNode.checked = a.checked || false; // 是否勾选
500    insertNode.data = a;
501    insertNode.addEventListener('change', (e: any) => {
502      if (e.detail && !this.multiple) {
503        this.nodeList.forEach((node) => {
504          node.checked = node.data!.key === insertNode.data!.key;
505        });
506      }
507      this.dispatchEvent(new CustomEvent('onChange', { detail: { data: insertNode.data, checked: e.detail } }));
508    });
509    this.nodeList.push(insertNode);
510    insertNode.checkable = this.getAttribute('checkable') || 'false';
511    insertNode.multiple = this.hasAttribute('multiple');
512    // @ts-ignore
513    li.data = a;
514    li.append(insertNode);
515    parent.append(li);
516    let ul: HTMLUListElement = document.createElement('ul');
517    // @ts-ignore
518    ul.open = 'true';
519    ul.style.transition = '.3s all';
520    this.setChildren(a,insertNode,li,ul);
521    // node 添加右键菜单功能
522    this.addedRightClickMenuFunction(insertNode);
523  }
524
525  addedRightClickMenuFunction(insertNode: LitTreeNode):void{
526    insertNode.oncontextmenu = (ev): void => {
527      ev.preventDefault();
528      this.selectedNode(insertNode);
529      this.currentSelectedNode = insertNode;
530      this.currentSelectedData = insertNode.data;
531      this.contextMenu!.style.display = 'block';
532      this.contextMenu!.style.left = ev.pageX + 'px';
533      this.contextMenu!.style.top = ev.pageY + 'px';
534    };
535  }
536
537  setDragableOfEvent(insertNode: LitTreeNode):void{
538    if (this.hasAttribute('dragable')) {
539      insertNode.draggable = true;
540      document.ondragover = function (e): void {
541        e.preventDefault();
542      };
543      //在拖动目标上触发事件 (源元素)
544      insertNode.ondrag = (ev): void => this.onDrag(ev); //元素正在拖动时触发
545      insertNode.ondragstart = (ev): undefined => this.onDragStart(ev); //用户开始拖动元素时触发
546      insertNode.ondragend = (ev): undefined => this.onDragEnd(ev); // 用户完成元素拖动后触发
547      //释放目标时触发的事件:
548      insertNode.ondragenter = (ev): undefined => this.onDragEnter(ev); //当被鼠标拖动的对象进入其容器范围内时触发此事件
549      insertNode.ondragover = (ev): undefined => this.onDragOver(ev); //当某被拖动的对象在另一对象容器范围内拖动时触发此事件
550      insertNode.ondragleave = (ev): undefined => this.onDragLeave(ev); //当被鼠标拖动的对象离开其容器范围内时触发此事件
551      insertNode.ondrop = (ev): undefined => this.onDrop(ev); //在一个拖动过程中,释放鼠标键时触发此事件
552    }
553  }
554
555  setChildren(a:any,insertNode: LitTreeNode,li: HTMLLIElement,ul: HTMLUListElement):void{
556    if (a.children && a.children.length > 0) {
557      if (this.hasAttribute('show-icon')) {
558        if (a.icon) {
559          insertNode.iconName = a.icon;
560        } else {
561          insertNode.iconName = 'folder';
562        }
563      } else {
564        insertNode.iconName = '';
565      }
566      insertNode.arrow = true;
567      li.append(ul);
568      this.drawTree(ul, a.children);
569    } else {
570      if (this.hasAttribute('show-icon')) {
571        if (a.icon) {
572          insertNode.iconName = a.icon;
573        } else {
574          insertNode.iconName = 'file';
575        }
576      } else {
577        insertNode.iconName = '';
578      }
579      insertNode.arrow = false;
580    }
581    li.onclick = (e): void => {
582      e.stopPropagation();
583      if (this.hasAttribute('foldable')) {
584        // @ts-ignore
585        if (li.data.children && li.data.children.length > 0) {
586          insertNode.autoExpand();
587        } else {
588          // @ts-ignore
589          this.dispatchEvent(new CustomEvent('onSelect', { detail: li.data }));
590          this.selectedNode(insertNode);
591        }
592      } else {
593        // @ts-ignore
594        this.dispatchEvent(new CustomEvent('onSelect', { detail: li.data }));
595        this.selectedNode(insertNode);
596      }
597    };
598  }
599
600  initElements(): void {
601    this.contextMenu = this.shadowRoot!.querySelector<HTMLDivElement>('#contextMenu');
602  }
603
604  initHtml(): string {
605    return `
606        <style>
607        :host{
608            display: flex;
609            color:#333;
610            width: 100%;
611            height: 100%;
612            overflow: auto;
613        }
614        :host *{
615            box-sizing: border-box;
616        }
617        ul,li{
618            list-style-type: none;
619            position:relative;
620            cursor:pointer;
621            overflow: hidden;
622        }
623        :host([show-line]) ul{
624            padding-left:10px;
625            border-left: 1px solid #d9d9d9;
626            overflow: hidden;
627        }
628        :host(:not([show-line])) ul{
629            padding-left: 10px;
630            overflow: hidden;
631        }
632        /*ul>li:after{content:' ';position:absolute;top:50%;left:-45px;width:45px;border:none;border-top:1px solid #d9d9d9;}*/
633        #root{
634            width: 100%;
635            height: 100%;
636        }
637        .context-menu {
638            position: absolute;
639            box-shadow: 0 0 10px #00000077;
640            pointer-events: auto;
641            z-index: 999;
642            transition: all .3s;
643        }
644        </style>
645        <ul id="root" style="margin: 0;overflow: hidden"></ul>
646        <div id="contextMenu" class="context-menu" style="display:none">
647            <slot name="contextMenu"></slot>
648        </div>
649        `;
650  }
651}
652
653if (!customElements.get('lit-tree')) {
654  customElements.define('lit-tree', LitTree);
655}
656