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