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';
17import { BaseElement, element } from '../BaseElement';
18import { type LitIcon } from '../icon/LitIcon';
19import { type TreeItemData } from './LitTree';
20import { LitTreeNodeHtmlStyle } from './LitTreeNode.html';
21
22@element('lit-tree-node')
23export class LitTreeNode extends BaseElement {
24  private arrowElement: HTMLSpanElement | null | undefined;
25  private itemElement: HTMLDivElement | null | undefined;
26  private checkboxElement: HTMLInputElement | null | undefined;
27  private iconElement: LitIcon | null | undefined;
28  private _data: TreeItemData | null | undefined;
29
30  static get observedAttributes(): string[] {
31    return [
32      'icon-name',
33      'icon-size',
34      'color',
35      'path',
36      'title',
37      'arrow',
38      'checkable',
39      'selected',
40      'checked',
41      'missing',
42      'multiple',
43      'top-depth',
44    ];
45  }
46
47  get checkable(): string {
48    return this.getAttribute('checkable') || 'false';
49  }
50
51  set data(value: TreeItemData | null | undefined) {
52    this._data = value;
53  }
54
55  get data(): TreeItemData | null | undefined {
56    return this._data;
57  }
58
59  set checkable(value) {
60    if (value === 'true') {
61      this.setAttribute('checkable', 'true');
62    } else {
63      this.setAttribute('checkable', 'false');
64    }
65  }
66
67  set multiple(value: boolean) {
68    if (value) {
69      this.setAttribute('multiple', '');
70    } else {
71      this.removeAttribute('multiple');
72    }
73  }
74
75  get multiple(): boolean {
76    return this.hasAttribute('multiple');
77  }
78
79  get iconName(): string {
80    return this.getAttribute('icon-name') || '';
81  }
82
83  set iconName(value) {
84    this.setAttribute('icon-name', value);
85  }
86
87  get topDepth(): boolean {
88    return this.hasAttribute('top-depth');
89  }
90
91  set topDepth(value) {
92    if (value) {
93      this.setAttribute('top-depth', '');
94    } else {
95      this.removeAttribute('top-depth');
96    }
97  }
98
99  get arrow(): boolean {
100    return this.hasAttribute('arrow');
101  }
102
103  set arrow(value) {
104    if (value) {
105      this.setAttribute('arrow', 'true');
106    } else {
107      this.removeAttribute('arrow');
108    }
109  }
110
111  get open(): string {
112    return this.getAttribute('open') || 'true';
113  }
114
115  set open(value) {
116    this.setAttribute('open', value);
117  }
118
119  get selected(): boolean {
120    return this.hasAttribute('selected');
121  }
122
123  set selected(value) {
124    if (value) {
125      this.setAttribute('selected', '');
126    } else {
127      this.removeAttribute('selected');
128    }
129  }
130
131  get checked(): boolean {
132    return this.hasAttribute('checked');
133  }
134
135  set checked(value) {
136    if (value === null || !value) {
137      this.removeAttribute('checked');
138    } else {
139      this.setAttribute('checked', '');
140    }
141  }
142
143  initElements(): void {
144    this.arrowElement = this.shadowRoot!.querySelector<HTMLSpanElement>('#arrow');
145    this.iconElement = this.shadowRoot!.querySelector<LitIcon>('#icon');
146    this.itemElement = this.shadowRoot!.querySelector<HTMLDivElement>('#item');
147    this.checkboxElement = this.shadowRoot!.querySelector<HTMLInputElement>('#checkbox');
148    this.arrowElement!.onclick = (e): void => {
149      e.stopPropagation();
150      this.autoExpand();
151    };
152    this.itemElement!.onclick = (e): void => {
153      e.stopPropagation();
154      if (this._data && this._data.disable === true) {
155        return;
156      }
157      this.onChange(!this.data?.checked);
158    };
159  }
160
161  onChange(checked: boolean): void {
162    this.checked = checked;
163    this.data!.checked = checked;
164    this.checkHandler();
165    this.dispatchEvent(new CustomEvent('change', { detail: checked }));
166  }
167
168  initHtml(): string {
169    return `
170         ${LitTreeNodeHtmlStyle}
171        </style>
172        <span id="arrow" style="margin-right: 2px"></span>
173        <div id="item" style="display: flex;align-items: center;padding-left: 2px">
174<!--            <lit-check-box id="checkbox"></lit-check-box>-->
175            <input id="checkbox" type="radio" style="cursor: pointer; pointer-events: none"/>
176            <lit-icon id="icon" name="${this.iconName}"></lit-icon>
177            <span id="title">${this.title}</span>
178        </div>
179        `;
180  }
181
182  //当 custom element首次被插入文档DOM时,被调用。
183  connectedCallback(): void {
184    if (this.hasAttribute('checked')) {
185      this.checkboxElement!.checked = true;
186    }
187    this.checkHandler();
188  }
189
190  checkHandler(): void {
191    if (this.checked) {
192      this.removeAttribute('missing');
193    }
194    if (this.hasAttribute('multiple')) {
195      if (this.nextElementSibling) {
196        if (this.checked) {
197          this.nextElementSibling.querySelectorAll('lit-tree-node').forEach((a: unknown) => {
198            //@ts-ignore
199            a.checked = true; //@ts-ignore
200            a.removeAttribute('missing');
201          });
202        } else {
203          //@ts-ignore
204          this.nextElementSibling.querySelectorAll('lit-tree-node').forEach((a: unknown) => (a.checked = false));
205        }
206      }
207      let setCheckStatus = (element: unknown): void => {
208        if (
209          //@ts-ignore
210          element.parentElement.parentElement.previousElementSibling && //@ts-ignore
211          element.parentElement.parentElement.previousElementSibling.tagName === 'LIT-TREE-NODE'
212        ) {
213          //@ts-ignore
214          let allChecked = Array.from(element.parentElement.parentElement.querySelectorAll('lit-tree-node')).every(
215            //@ts-ignore
216            (item: unknown) => item.checked
217          ); //@ts-ignore
218          let someChecked = Array.from(element.parentElement.parentElement.querySelectorAll('lit-tree-node')).some(
219            //@ts-ignore
220            (item: unknown, index, array) => item.checked
221          );
222          if (allChecked === true) {
223            //@ts-ignore
224            element.parentElement.parentElement.previousElementSibling.checked = true; //@ts-ignore
225            element.parentElement.parentElement.previousElementSibling.removeAttribute('missing');
226          } else if (someChecked) {
227            //@ts-ignore
228            element.parentElement.parentElement.previousElementSibling.setAttribute('missing', ''); //@ts-ignore
229            element.parentElement.parentElement.previousElementSibling.removeAttribute('checked');
230          } else {
231            //@ts-ignore
232            element.parentElement.parentElement.previousElementSibling.removeAttribute('missing'); //@ts-ignore
233            element.parentElement.parentElement.previousElementSibling.removeAttribute('checked');
234          } //@ts-ignore
235          setCheckStatus(element.parentElement.parentElement.previousElementSibling);
236        }
237      };
238      setCheckStatus(this);
239    }
240  }
241
242  expand(): void {
243    if (this.open === 'true') {
244      return;
245    }
246    let uul = this.parentElement!.querySelector('ul');
247    this.expandSection(uul);
248    this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(0deg)';
249  }
250
251  collapse(): void {
252    if (this.open === 'false') {
253      return;
254    }
255    let uul = this.parentElement!.querySelector('ul');
256    this.collapseSection(uul);
257    this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(-90deg)';
258  }
259
260  autoExpand(): void {
261    let uul = this.parentElement!.querySelector('ul');
262    if (this.open === 'true') {
263      this.collapseSection(uul);
264      this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(-90deg)';
265    } else {
266      this.expandSection(uul);
267      this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(0deg)';
268    }
269  }
270
271  //收起
272  collapseSection(element: unknown): void {
273    if (!element) {
274      return;
275    } //@ts-ignore
276    let sectionHeight = element.scrollHeight; //@ts-ignore
277    let elementTransition = element.style.transition; //@ts-ignore
278    element.style.transition = '';
279    requestAnimationFrame(function () {
280      //@ts-ignore
281      element.style.height = sectionHeight + 'px'; //@ts-ignore
282      element.style.transition = elementTransition;
283      requestAnimationFrame(function () {
284        //@ts-ignore
285        element.style.height = 0 + 'px';
286      });
287    });
288    this.open = 'false';
289  }
290
291  //展开
292  expandSection(element: unknown): void {
293    if (!element) {
294      return;
295    } //@ts-ignore
296    let sectionHeight = element.scrollHeight; //@ts-ignore
297    element.style.height = sectionHeight + 'px'; //@ts-ignore
298    element.ontransitionend = (e: unknown): void => {
299      //@ts-ignore
300      element.ontransitionend = null; //@ts-ignore
301      element.style.height = null;
302      this.open = 'true';
303    };
304  }
305
306  //当 custom element从文档DOM中删除时,被调用。
307  disconnectedCallback(): void {}
308
309  //当 custom element被移动到新的文档时,被调用。
310  adoptedCallback(): void {}
311
312  //当 custom element增加、删除、修改自身属性时,被调用。
313  attributeChangedCallback(name: string, oldValue: unknown, newValue: unknown): void {
314    if (name === 'title') {
315      //@ts-ignore
316      this.shadowRoot!.querySelector('#title')!.textContent = newValue;
317    } else if (name === 'icon-name') {
318      if (this.iconElement) {
319        if (newValue !== null && newValue !== '' && newValue !== 'null') {
320          //@ts-ignore
321          this.iconElement!.setAttribute('name', newValue);
322          this.iconElement!.style.display = 'flex';
323        } else {
324          this.iconElement!.style.display = 'none';
325        }
326      }
327    } else if (name === 'checkable') {
328      if (this.checkboxElement) {
329        if (newValue === 'true' && this._data!.disable !== true) {
330          this.checkboxElement!.style.display = 'inline-block';
331        } else {
332          this.checkboxElement!.style.display = 'none';
333        }
334      }
335    } else if (name === 'checked') {
336      if (this.checkboxElement) {
337        this.checkboxElement.checked = this.hasAttribute('checked');
338      }
339    }
340    if (this.arrow) {
341      this.checkboxElement!.style.display = 'none';
342    }
343  }
344
345  //在node top  top-right  bottom bottom-right 画线条
346  drawLine(direction: string /*string[top|bottom|top-right|bottom-right]*/): void {
347    let item = this.shadowRoot!.querySelector('#item');
348    if (!item) {
349      return;
350    }
351    item.removeAttribute('line-top');
352    item.removeAttribute('line-top-right');
353    item.removeAttribute('line-bottom');
354    item.removeAttribute('line-bottom-right');
355    switch (direction) {
356      case 'top':
357        item.setAttribute('line-top', '');
358        break;
359      case 'bottom':
360        item.setAttribute('line-bottom', '');
361        break;
362      case 'top-right':
363        item.setAttribute('line-top-right', '');
364        break;
365      case 'bottom-right':
366        item.setAttribute('line-bottom-right', '');
367        break;
368    }
369  }
370}
371
372if (!customElements.get('lit-tree-node')) {
373  customElements.define('lit-tree-node', LitTreeNode);
374}
375