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: any) => { 198 a.checked = true; 199 a.removeAttribute('missing'); 200 }); 201 } else { 202 this.nextElementSibling.querySelectorAll('lit-tree-node').forEach((a: any) => (a.checked = false)); 203 } 204 } 205 let setCheckStatus = (element: any): void => { 206 if ( 207 element.parentElement.parentElement.previousElementSibling && 208 element.parentElement.parentElement.previousElementSibling.tagName === 'LIT-TREE-NODE' 209 ) { 210 let allChecked = Array.from(element.parentElement.parentElement.querySelectorAll('lit-tree-node')).every( 211 (item: any) => item.checked 212 ); 213 let someChecked = Array.from(element.parentElement.parentElement.querySelectorAll('lit-tree-node')).some( 214 (item: any, index, array) => item.checked 215 ); 216 if (allChecked === true) { 217 element.parentElement.parentElement.previousElementSibling.checked = true; 218 element.parentElement.parentElement.previousElementSibling.removeAttribute('missing'); 219 } else if (someChecked) { 220 element.parentElement.parentElement.previousElementSibling.setAttribute('missing', ''); 221 element.parentElement.parentElement.previousElementSibling.removeAttribute('checked'); 222 } else { 223 element.parentElement.parentElement.previousElementSibling.removeAttribute('missing'); 224 element.parentElement.parentElement.previousElementSibling.removeAttribute('checked'); 225 } 226 setCheckStatus(element.parentElement.parentElement.previousElementSibling); 227 } 228 }; 229 setCheckStatus(this); 230 } 231 } 232 233 expand(): void { 234 if (this.open === 'true') { 235 return; 236 } 237 let uul = this.parentElement!.querySelector('ul'); 238 this.expandSection(uul); 239 this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(0deg)'; 240 } 241 242 collapse(): void { 243 if (this.open === 'false') { 244 return; 245 } 246 let uul = this.parentElement!.querySelector('ul'); 247 this.collapseSection(uul); 248 this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(-90deg)'; 249 } 250 251 autoExpand(): void { 252 let uul = this.parentElement!.querySelector('ul'); 253 if (this.open === 'true') { 254 this.collapseSection(uul); 255 this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(-90deg)'; 256 } else { 257 this.expandSection(uul); 258 this.arrowElement!.style.transform = 'translateX(-50%) rotateZ(0deg)'; 259 } 260 } 261 262 //收起 263 collapseSection(element: any): void { 264 if (!element) { 265 return; 266 } 267 let sectionHeight = element.scrollHeight; 268 let elementTransition = element.style.transition; 269 element.style.transition = ''; 270 requestAnimationFrame(function () { 271 element.style.height = sectionHeight + 'px'; 272 element.style.transition = elementTransition; 273 requestAnimationFrame(function () { 274 element.style.height = 0 + 'px'; 275 }); 276 }); 277 this.open = 'false'; 278 } 279 280 //展开 281 expandSection(element: any): void { 282 if (!element) { 283 return; 284 } 285 let sectionHeight = element.scrollHeight; 286 element.style.height = sectionHeight + 'px'; 287 element.ontransitionend = (e: any): void => { 288 element.ontransitionend = null; 289 element.style.height = null; 290 this.open = 'true'; 291 }; 292 } 293 294 //当 custom element从文档DOM中删除时,被调用。 295 disconnectedCallback(): void {} 296 297 //当 custom element被移动到新的文档时,被调用。 298 adoptedCallback(): void {} 299 300 //当 custom element增加、删除、修改自身属性时,被调用。 301 attributeChangedCallback(name: string, oldValue: any, newValue: any): void { 302 if (name === 'title') { 303 this.shadowRoot!.querySelector('#title')!.textContent = newValue; 304 } else if (name === 'icon-name') { 305 if (this.iconElement) { 306 if (newValue !== null && newValue !== '' && newValue !== 'null') { 307 this.iconElement!.setAttribute('name', newValue); 308 this.iconElement!.style.display = 'flex'; 309 } else { 310 this.iconElement!.style.display = 'none'; 311 } 312 } 313 } else if (name === 'checkable') { 314 if (this.checkboxElement) { 315 if (newValue === 'true' && this._data!.disable !== true) { 316 this.checkboxElement!.style.display = 'inline-block'; 317 } else { 318 this.checkboxElement!.style.display = 'none'; 319 } 320 } 321 } else if (name === 'checked') { 322 if (this.checkboxElement) { 323 this.checkboxElement.checked = this.hasAttribute('checked'); 324 } 325 } 326 } 327 328 //在node top top-right bottom bottom-right 画线条 329 drawLine(direction: string /*string[top|bottom|top-right|bottom-right]*/): void { 330 let item = this.shadowRoot!.querySelector('#item'); 331 if (!item) { 332 return; 333 } 334 item.removeAttribute('line-top'); 335 item.removeAttribute('line-top-right'); 336 item.removeAttribute('line-bottom'); 337 item.removeAttribute('line-bottom-right'); 338 switch (direction) { 339 case 'top': 340 item.setAttribute('line-top', ''); 341 break; 342 case 'bottom': 343 item.setAttribute('line-bottom', ''); 344 break; 345 case 'top-right': 346 item.setAttribute('line-top-right', ''); 347 break; 348 case 'bottom-right': 349 item.setAttribute('line-bottom-right', ''); 350 break; 351 } 352 } 353} 354 355if (!customElements.get('lit-tree-node')) { 356 customElements.define('lit-tree-node', LitTreeNode); 357} 358