• 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 '../icon/LitIcon.js'
17import '../checkbox/LitCheckBox.js'
18import { BaseElement, element } from '../BaseElement.js';
19import { LitCheckBox } from '../checkbox/LitCheckBox.js';
20import { LitIcon } from '../icon/LitIcon.js';
21import { TreeItemData } from './LitTree.js';
22
23@element('lit-tree-node')
24export class LitTreeNode extends BaseElement {
25
26  private arrowElement: HTMLSpanElement | null | undefined;
27  private itemElement: HTMLDivElement | null | undefined;
28  private checkboxElement: LitCheckBox | null | undefined;
29  private iconElement: LitIcon | null | undefined;
30  private _data: TreeItemData | null | undefined;
31
32  static get observedAttributes() {
33    return ['icon-name', 'icon-size', 'color', 'path', 'title', 'arrow', 'checkable', 'selected', 'checked', 'missing', 'multiple', 'top-depth'];
34  }
35
36  get checkable() {
37    return this.getAttribute('checkable') || 'false';
38  }
39
40  set data(value: TreeItemData | null | undefined) {
41    this._data = value;
42  }
43
44  get data() {
45    return this._data;
46  }
47
48  set checkable(value) {
49    if (value === 'true') {
50      this.setAttribute('checkable', 'true');
51    } else {
52      this.setAttribute('checkable', 'false');
53    }
54  }
55
56  set multiple(value: boolean) {
57    if (value) {
58      this.setAttribute('multiple', '');
59    } else {
60      this.removeAttribute('multiple');
61    }
62  }
63
64  get multiple() {
65    return this.hasAttribute('multiple');
66  }
67
68
69  get iconName() {
70    return this.getAttribute('icon-name') || '';
71  }
72
73  set iconName(value) {
74    this.setAttribute('icon-name', value);
75  }
76
77  get topDepth() {
78    return this.hasAttribute('top-depth');
79  }
80
81  set topDepth(value) {
82    if (value) {
83      this.setAttribute('top-depth', '');
84    } else {
85      this.removeAttribute('top-depth');
86    }
87  }
88
89  get arrow() {
90    return this.hasAttribute('arrow');
91  }
92
93  set arrow(value) {
94    if (value) {
95      this.setAttribute('arrow', 'true');
96    } else {
97      this.removeAttribute('arrow');
98    }
99  }
100
101  get open() {
102    return this.getAttribute('open') || 'true';
103  }
104
105  set open(value) {
106    this.setAttribute('open', value);
107  }
108
109  get selected() {
110    return this.hasAttribute('selected');
111  }
112
113  set selected(value) {
114    if (value) {
115      this.setAttribute('selected', '');
116    } else {
117      this.removeAttribute('selected');
118    }
119  }
120
121  get checked() {
122    return this.hasAttribute('checked');
123  }
124
125  set checked(value) {
126    if (value === null || !value) {
127      this.removeAttribute('checked');
128    } else {
129      this.setAttribute('checked', '');
130    }
131  }
132
133  initElements(): void {
134    this.arrowElement = this.shadowRoot!.querySelector<HTMLSpanElement>('#arrow');
135    this.iconElement = this.shadowRoot!.querySelector<LitIcon>('#icon');
136    this.itemElement = this.shadowRoot!.querySelector<HTMLDivElement>('#item');
137    this.checkboxElement = this.shadowRoot!.querySelector<LitCheckBox>('#checkbox');
138    this.arrowElement!.onclick = (e) => {
139      e.stopPropagation();
140      this.autoExpand();
141    }
142    this.checkboxElement!.onchange = (e: any) => {
143      e.stopPropagation();
144      this.onChange(e.detail.checked);
145      return false;
146    }
147    //这里需要给checkbox 添加onclick时间 并停止冒泡,不然onchange事件会触发父节点中的 onclick事件
148    this.checkboxElement!.onclick = (e) => {
149      e.stopPropagation();
150    };
151    this.itemElement!.onclick = (e) => {
152      e.stopPropagation();
153      this.onChange(!this.data?.checked);
154    };
155  }
156
157  onChange(checked: boolean) {
158    this.checked = checked;
159    this.data!.checked = checked;
160    this.checkHandler();
161    this.dispatchEvent(new CustomEvent('change', {detail: checked}));
162  }
163
164  initHtml(): string {
165    return `
166        <style>
167        :host{
168            display: flex;
169            margin: 0;
170            align-items: center;
171         }
172         :host(:hover) #item{
173            background-color: #f5f5f5;
174            border-radius: 4px;
175         }
176
177         :host(:not([arrow]))  #arrow{
178            display: none;
179         }
180
181         :host(:not([arrow]))  #item{
182            margin-left: 15px;
183         }
184
185         :host([top-depth])  #item{
186            margin-left: 0;
187         }
188
189         #title{
190            padding: 4px 6px;
191         }
192
193         #arrow{
194            width: 0;
195            height: 0;
196            border-top: 8px solid #262626;
197            border-bottom: 0px solid transparent;
198            border-left: 5px solid transparent;
199            border-right: 5px solid transparent;
200            transition: all .3s ;
201            transform: translateX(-50%) rotateZ(0deg);
202            margin-left: 5px;
203         }
204
205         #icon{
206            display: none;
207            margin-left: 5px;
208         }
209
210         /*画拖动辅助线*/
211         #item[line-top]{
212            position: relative;
213            width: 100%;
214         }
215
216         #item[line-top]::before{
217            content: '';
218            position: absolute;
219            top: 5px;
220            left: 0;
221            transform: translateY(-50%);
222            width: 6px;
223            height: 6px;
224            overflow: visible;
225            z-index: 999;
226            background-color: #fff;
227            border-radius: 6px;
228            border: 2px solid #42b983;
229         }
230         #item[line-top]::after{
231            content: '';
232            overflow: visible;
233            position: absolute;
234            z-index: 999;
235            top: 4px;
236            left: 10px;
237            width: 100%;
238            height: 2px;
239            background-color: #42b983;
240         }
241
242         #item[line-bottom]{
243            position: relative;
244            width: 100%;
245         }
246         #item[line-bottom]::before{
247            content: '';
248            position: absolute;
249            bottom: 5px;
250            left: 0;
251            transform: translateY(50%);
252            width: 6px;
253            height: 6px;
254            overflow: visible;
255            z-index: 999;
256            background-color: #fff;
257            border-radius: 6px;
258            border: 2px solid #42b983;
259         }
260         #item[line-bottom]::after{
261            content: '';
262            overflow: visible;
263            position: absolute;
264            z-index: 999;
265            bottom: 4px;
266            left: 10px;
267            width: 100%;
268            height: 2px;
269            background-color: #42b983;
270         }
271         #item[line-bottom-right]{
272            position: relative;
273            width: 100%;
274         }
275         #item[line-bottom-right]::before{
276            content: '';
277            position: absolute;
278            bottom: 5px;
279            left: 20px;
280            transform: translateY(50%);
281            width: 6px;
282            height: 6px;
283            overflow: visible;
284            z-index: 999;
285            background-color: #fff;
286            border-radius: 6px;
287            border: 2px solid #42b983;
288         }
289         #item[line-bottom-right]::after{
290            content: '';
291            overflow: visible;
292            position: absolute;
293            z-index: 999;
294            bottom: 4px;
295            left: 30px;
296            width: 100%;
297            height: 2px;
298            background-color: #42b983;
299         }
300         :host([missing]) #checkbox{
301            position: relative;
302         }
303
304         :host([missing]) #checkbox::after{
305            content: '';
306            width: calc(100% - 20px);
307            height: calc(100% - 8px);
308            box-sizing: border-box;
309            top: 0;
310            left: 0;
311            margin: 4px;
312            background-color: #3391FF;
313            position: absolute;
314         }
315
316        </style>
317        <span id="arrow" style="margin-right: 2px"></span>
318        <div id="item" style="display: flex;align-items: center;padding-left: 2px">
319            <lit-check-box id="checkbox"></lit-check-box>
320            <lit-icon id="icon" name="${this.iconName}"></lit-icon>
321            <span id="title">${this.title}</span>
322        </div>
323        `
324  }
325
326  //当 custom element首次被插入文档DOM时,被调用。
327  connectedCallback() {
328    if (this.hasAttribute('checked')) this.checkboxElement!.checked = true;
329    this.checkHandler();
330  }
331
332  checkHandler() {
333    if (this.checked) {
334      this.removeAttribute('missing');
335    }
336    if (this.hasAttribute('multiple')) {
337      if (this.nextElementSibling) {
338        if (this.checked) {
339          this.nextElementSibling.querySelectorAll('lit-tree-node').forEach((a: any) => {
340            a.checked = true;
341            a.removeAttribute('missing');
342          });
343        } else {
344          this.nextElementSibling.querySelectorAll('lit-tree-node').forEach((a: any) => a.checked = false);
345        }
346      }
347      let setCheckStatus = (element: any) => {
348        if (element.parentElement.parentElement.previousElementSibling && element.parentElement.parentElement.previousElementSibling.tagName === 'LIT-TREE-NODE') {
349          let allChecked = Array.from(element.parentElement.parentElement.querySelectorAll('lit-tree-node')).every((item: any) => item.checked);
350          let someChecked = Array.from(element.parentElement.parentElement.querySelectorAll('lit-tree-node')).some((item: any, index, array) => item.checked);
351          if (allChecked === true) {
352            element.parentElement.parentElement.previousElementSibling.checked = true;
353            element.parentElement.parentElement.previousElementSibling.removeAttribute('missing');
354          } else if (someChecked) {
355            element.parentElement.parentElement.previousElementSibling.setAttribute('missing', '')
356            element.parentElement.parentElement.previousElementSibling.removeAttribute('checked')
357          } else {
358            element.parentElement.parentElement.previousElementSibling.removeAttribute('missing')
359            element.parentElement.parentElement.previousElementSibling.removeAttribute('checked')
360          }
361          setCheckStatus(element.parentElement.parentElement.previousElementSibling)
362        }
363      }
364      setCheckStatus(this);
365    }
366  }
367
368  expand() {
369    if (this.open === 'true') return;
370    let uul = this.parentElement!.querySelector('ul');
371    this.expandSection(uul);
372    this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(0deg)';
373  }
374
375  collapse() {
376    if (this.open === 'false') return;
377    let uul = this.parentElement!.querySelector('ul');
378    this.collapseSection(uul);
379    this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(-90deg)';
380  }
381
382  autoExpand() {
383    let uul = this.parentElement!.querySelector('ul');
384    if (this.open === 'true') {
385      this.collapseSection(uul);
386      this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(-90deg)';
387    } else {
388      this.expandSection(uul);
389      this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(0deg)';
390    }
391  }
392
393  //收起
394  collapseSection(element: any) {
395    if (!element) return;
396    let sectionHeight = element.scrollHeight;
397    let elementTransition = element.style.transition;
398    element.style.transition = '';
399    requestAnimationFrame(function () {
400      element.style.height = sectionHeight + 'px';
401      element.style.transition = elementTransition;
402      requestAnimationFrame(function () {
403        element.style.height = 0 + 'px';
404      });
405    });
406    this.open = 'false';
407  }
408
409  //展开
410  expandSection(element: any) {
411    if (!element) return;
412    let sectionHeight = element.scrollHeight;
413    element.style.height = sectionHeight + 'px';
414    element.ontransitionend = (e: any) => {
415      element.ontransitionend = null;
416      element.style.height = null;
417      this.open = 'true';
418    };
419  }
420
421  //当 custom element从文档DOM中删除时,被调用。
422  disconnectedCallback() {
423
424  }
425
426  //当 custom element被移动到新的文档时,被调用。
427  adoptedCallback() {
428
429  }
430
431  //当 custom element增加、删除、修改自身属性时,被调用。
432  attributeChangedCallback(name: string, oldValue: any, newValue: any) {
433    if (name === 'title') {
434      this.shadowRoot!.querySelector('#title')!.textContent = newValue;
435    } else if (name === 'icon-name') {
436      if (this.iconElement) {
437        if (newValue !== null && newValue !== '' && newValue !== 'null') {
438          this.iconElement!.setAttribute('name', newValue);
439          this.iconElement!.style.display = 'flex';
440        } else {
441          this.iconElement!.style.display = 'none';
442        }
443      }
444    } else if (name === 'checkable') {
445      if (this.checkboxElement) {
446        if (newValue === 'true') {
447          this.checkboxElement!.style.display = 'inline-block';
448        } else {
449          this.checkboxElement!.style.display = 'none';
450        }
451      }
452    } else if (name === 'checked') {
453      if (this.checkboxElement) {
454        this.checkboxElement.checked = this.hasAttribute('checked');
455      }
456    }
457  }
458
459  //在node top  top-right  bottom bottom-right 画线条
460  drawLine(direction: string/*string[top|bottom|top-right|bottom-right]*/) {
461    let item = this.shadowRoot!.querySelector('#item');
462    if (!item) return;
463    item.removeAttribute('line-top');
464    item.removeAttribute('line-top-right');
465    item.removeAttribute('line-bottom');
466    item.removeAttribute('line-bottom-right');
467    switch (direction) {
468      case 'top':
469        item.setAttribute('line-top', '')
470        break;
471      case 'bottom':
472        item.setAttribute('line-bottom', '')
473        break;
474      case 'top-right':
475        item.setAttribute('line-top-right', '')
476        break;
477      case 'bottom-right':
478        item.setAttribute('line-bottom-right', '')
479        break;
480    }
481  }
482}
483
484if (!customElements.get('lit-tree-node')) {
485  customElements.define('lit-tree-node', LitTreeNode);
486}