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 {element} from "../BaseElement.js"; 17import {LitTabpane} from "./lit-tabpane.js"; 18 19@element('lit-tabs') 20export class LitTabs extends HTMLElement { 21 private tabPos: any 22 private nav: HTMLDivElement | undefined | null 23 private line: HTMLDivElement | undefined | null 24 private slots: HTMLSlotElement | undefined | null 25 26 constructor() { 27 super(); 28 const shadowRoot = this.attachShadow({mode: 'open'}); 29 shadowRoot.innerHTML = ` 30 <style> 31 :host{ 32 display: block; 33 text-align: unset; 34 color: var(--dark-color1,#252525); 35 background-color: var(--dark-background,#FFFFFF); 36 box-shadow: #00000033 0 0 10px ; 37 } 38 ::slotted(lit-tabpane){ 39 box-sizing:border-box; 40 width:100%; 41 height:100%; 42 flex-shrink:0; 43 overflow:auto; 44 } 45 .nav-item{ 46 display: inline-flex; 47 justify-content: center; 48 align-items: center; 49 padding: 6px 0px 6px 12px; 50 font-size: .9rem; 51 font-weight: normal; 52 cursor: pointer; 53 transition: all 0.3s; 54 flex-shrink: 0; 55 } 56 .nav-item lit-icon{ 57 margin-right: 2px; 58 font-size: inherit; 59 } 60 .nav-item[data-disabled]{ 61 pointer-events: all; 62 cursor: not-allowed; 63 color: #bfbfbf; 64 } 65 .nav-item[data-hidden]{ 66 pointer-events: all; 67 cursor: not-allowed; 68 color: #bfbfbf; 69 display: none; 70 } 71 .tab-content{ 72 display: block; 73 background-color: #fff; 74 flex:1; 75 } 76 :host(:not([position])) .nav-root, 77 :host([position^='top']) .nav-root{ 78 display: flex; 79 position: relative; 80 justify-content: center; 81 align-items: center; 82 } 83 :host(:not([mode]):not([position])) .tab-line, 84 :host([mode='flat'][position^='top']) .tab-line{ 85 position:absolute; 86 } 87 88 :host(:not([position])) .tab-nav-container, 89 :host([position^='top']) .tab-nav-container{ 90 display: flex; 91 position: relative; 92 flex-direction: column; 93 overflow-y: hidden; 94 overflow-x: auto; 95 overflow: -moz-scrollbars-none; 96 -ms-overflow-style: none; 97 transition: all 0.3s; 98 flex: 1; 99 } 100 :host([position='top']) .tab-nav, 101 :host([position='top-left']) .tab-nav{ 102 display: flex; 103 position: relative; 104 justify-content: flex-start; 105 cursor: row-resize; 106 user-select: none; 107 margin-top: 6px; 108 margin-left: 5px; 109 } 110 :host([position='top-center']) .tab-nav{ 111 display: flex; 112 justify-content: center; 113 } 114 :host([position='top-right']) .tab-nav{ 115 display: flex; 116 justify-content: flex-end; 117 } 118 119 :host([position^='top'][mode='card']) .nav-item{ 120 border-top: 1px solid var(--dark-border1,#f0f0f0); 121 border-left: 1px solid var(--dark-border1,#f0f0f0); 122 border-right: 1px solid var(--dark-border1,#f0f0f0); 123 bottom: 0px; 124 margin-right: 2px; 125 position: relative; 126 height: 100%; 127 } 128 :host([position^='top']) .tab-nav-bg-line{ 129 position: absolute;bottom: 0;height: 1px; 130 width: 100% 131 } 132 :host([position^='top'][mode='card']) .nav-item:not([data-selected]){ 133 border-top-left-radius: 5px; 134 border-top-right-radius: 5px; 135 background-color: var(--dark-border1,#D8D8D8); 136 color: var(--dark-color1,#212121); 137 } 138 :host([position^='top'][mode='card']) .nav-item[data-selected]{ 139 background-color: var(--dark-background,#ffffff); 140 bottom: 0px; 141 color: var(--dark-color1,#212121); 142 border-top: 1px solid var(--dark-border1,#bababa); 143 border-top-left-radius: 5px; 144 border-top-right-radius: 5px; 145 border-left: 1px solid var(--dark-border1,#bababa); 146 border-right: 1px solid var(--dark-border1,#bababa); 147 } 148 149 :host([position^='bottom']) .tab{ 150 display: flex; 151 flex-direction: column-reverse; 152 } 153 :host([mode='flat'][position^='bottom']) .tab-line{ 154 position:absolute; 155 top: -3px; 156 background-color: #42b983; 157 height: 2px; 158 transform: translateY(-100%); 159 transition: all 0.3s; 160 } 161 :host([position^='bottom']) .tab-nav-container{ 162 display: flex; 163 position: relative; 164 flex-direction: column; 165 overflow-x: auto; 166 overflow-y: visible; 167 overflow: -moz-scrollbars-none; 168 -ms-overflow-style: none; 169 transition: all 0.3s; 170 flex: 1; 171 border-top: #f0f0f0 1px solid; 172 } 173 :host([position^='bottom']) .nav-root{ 174 display: flex; 175 justify-content: center; 176 align-items: center; 177 } 178 :host([position='bottom']) .tab-nav, 179 :host([position='bottom-left']) .tab-nav{ 180 display: flex; 181 position: relative; 182 justify-content: flex-start; 183 } 184 :host([position='bottom-center']) .tab-nav{ 185 display: flex; 186 justify-content: center; 187 } 188 :host([position='bottom-right']) .tab-nav{ 189 display: flex; 190 justify-content: flex-end; 191 } 192 :host([position^='bottom'][mode='card']) .nav-item{ 193 border-top: 1px solid #ffffff; 194 border-left: 1px solid #f0f0f0; 195 border-right: 1px solid #f0f0f0; 196 border-bottom: 1px solid #f0f0f0; 197 top: -1px; 198 margin-right: 2px; 199 position: relative; 200 } 201 :host([position^='bottom']) .tab-nav-bg-line{ 202 position: absolute;top: 0;height: 1px;background-color: #f0f0f0;width: 100% 203 } 204 :host([position^='bottom'][mode='card']) .nav-item:not([data-selected]){ 205 background-color: #f5f5f5; 206 border-top: 1px solid #f0f0f0; 207 } 208 :host([position^='bottom'][mode='card']) .nav-item[data-selected]{ 209 background-color: #ffffff; 210 border-top: 1px solid #fff; 211 top: -1px; 212 } 213 214 :host([position^='left']) .tab{ 215 display: flex; 216 flex-direction: row; 217 } 218 :host([mode='flat'][position^='left']) .tab-line{ 219 position:absolute; 220 right: 1px; 221 background-color: #42b983; 222 width: 3px; 223 transform: translateX(100%); 224 transition: all 0.3s; 225 } 226 :host([position^='left']) .tab-nav-container{ 227 display: flex; 228 position: relative; 229 flex-direction: row; 230 overflow-x: auto; 231 overflow-y: visible; 232 overflow: -moz-scrollbars-none; 233 -ms-overflow-style: none; 234 transition: all 0.3s; 235 flex: 1; 236 border-right: #f0f0f0 1px solid; 237 } 238 :host([position^='left']) .nav-root{ 239 display: flex; 240 flex-direction: column; 241 justify-content: center; 242 align-items: center; 243 } 244 :host([position='left']) .tab-nav, 245 :host([position='left-top']) .tab-nav{ 246 display: flex; 247 position: relative; 248 flex-direction: column; 249 justify-content: flex-start; 250 } 251 :host([position='left-center']) .tab-nav{ 252 display: flex; 253 position: relative; 254 flex-direction: column; 255 justify-content: center; 256 } 257 :host([position='left-bottom']) .tab-nav{ 258 display: flex; 259 position: relative; 260 flex-direction: column; 261 justify-content: flex-end; 262 } 263 :host([position^='left'][mode='card']) .nav-item{ 264 border-top: 1px solid #f0f0f0; 265 border-left: 1px solid #f0f0f0; 266 border-right: 1px solid #ffffff; 267 border-bottom: 1px solid #f0f0f0; 268 right: -1px; 269 margin-bottom: 2px; 270 position: relative; 271 } 272 :host([position^='left']) .tab-nav-bg-line{ 273 position: absolute;right: 0;width: 1px;background-color: #f0f0f0;width: 100% 274 } 275 :host([position^='left'][mode='card']) .nav-item:not([data-selected]){ 276 background-color: #f5f5f5; 277 border-right: 1px solid #f0f0f0; 278 } 279 :host([position^='left'][mode='card']) .nav-item[data-selected]{ 280 background-color: #ffffff; 281 border-bottom: 1px solid #fff; 282 right: -1px; 283 } 284 285 :host([position^='right']) .tab{ 286 display: flex; 287 flex-direction: row-reverse; 288 } 289 :host([mode='flat'][position^='right']) .tab-line{ 290 position:absolute; 291 left: 1px; 292 background-color: #42b983; 293 width: 3px; 294 transform: translateX(-100%); 295 transition: all 0.3s; 296 } 297 :host([position^='right']) .tab-nav-container{ 298 display: flex; 299 position: relative; 300 flex-direction: row-reverse; 301 overflow-x: auto; 302 overflow-y: visible; 303 overflow: -moz-scrollbars-none; 304 -ms-overflow-style: none; 305 transition: all 0.3s; 306 flex: 1; 307 border-left: #f0f0f0 1px solid; 308 } 309 :host([position^='right']) .nav-root{ 310 display: flex; 311 flex-direction: column; 312 justify-content: center; 313 align-items: center; 314 } 315 :host([position='right']) .tab-nav, 316 :host([position='right-top']) .tab-nav{ 317 display: flex; 318 position: relative; 319 flex-direction: column; 320 justify-content: flex-start; 321 } 322 :host([position='right-center']) .tab-nav{ 323 display: flex; 324 position: relative; 325 flex-direction: column; 326 justify-content: center; 327 } 328 :host([position='right-bottom']) .tab-nav{ 329 display: flex; 330 position: relative; 331 flex-direction: column; 332 justify-content: flex-end; 333 } 334 :host([position^='right'][mode='card']) .nav-item{ 335 border-top: 1px solid #f0f0f0; 336 border-left: 1px solid #ffffff; 337 border-right: 1px solid #f0f0f0; 338 border-bottom: 1px solid #f0f0f0; 339 left: -1px; 340 margin-top: 2px; 341 position: relative; 342 } 343 :host([position^='right']) .tab-nav-bg-line{ 344 position: absolute;left: 0;width: 1px;background-color: #f0f0f0;width: 100% 345 } 346 :host([position^='right'][mode='card']) .nav-item:not([data-selected]){ 347 background-color: #f5f5f5; 348 border-left: 1px solid #f0f0f0; 349 } 350 :host([position^='right'][mode='card']) .nav-item[data-selected]{ 351 background-color: #ffffff; 352 left: -1px; 353 } 354 355 356 .tab-nav-container::-webkit-scrollbar { 357 display: none; 358 } 359 360 .close-icon:hover{ 361 color: #000; 362 } 363 .nav-item[data-closeable] .close-icon{ 364 display: block; 365 padding: 2px 5px; 366 color: var(--dark-icon,#606060) 367 } 368 .nav-item[data-closeable] .no-close-icon{ 369 display: none; 370 } 371 .nav-item:not([data-closeable]) .no-close-icon{ 372 display: block; 373 } 374 .nav-item:not([data-closeable]) .close-icon{ 375 display: none; 376 } 377 378 </style> 379 <style id="filter"></style> 380 <div class="tab" > 381 <div class="nav-root" style="background-color: var(--dark-background4,#f2f2f2);"> 382 <slot name="left" style="flex:1"></slot> 383 <div class="tab-nav-container" > 384 <div class="tab-nav-bg-line"></div> 385 <div class="tab-nav" id="nav" ></div> 386 <div class="tab-line" id="tab-line"></div> 387 </div> 388 <slot name="right" style="flex:1"></slot> 389 </div> 390 <div class="tab-content"> 391 <slot id="slot">NEED CONTENT</slot> 392 </div> 393 </div> 394 ` 395 } 396 397 static get observedAttributes() { 398 return ['activekey', 'mode', 'position'] 399 } 400 401 get position() { 402 return this.getAttribute('position') || 'top'; 403 } 404 405 set position(value) { 406 this.setAttribute('position', value); 407 } 408 409 get mode() { 410 return this.getAttribute('mode') || 'flat'; 411 } 412 413 set mode(value) { 414 this.setAttribute('mode', value); 415 } 416 417 get activekey() { 418 return this.getAttribute("activekey") || ''; 419 } 420 421 set activekey(value: string) { 422 this.setAttribute('activekey', value); 423 } 424 425 set onTabClick(fn: any) { 426 this.addEventListener('onTabClick', fn); 427 } 428 429 updateLabel(key: string, value: string) { 430 if (this.nav) { 431 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 432 if (item) { 433 item.querySelector<HTMLSpanElement>("span")!.innerHTML = value; 434 this.initTabPos() 435 } 436 } 437 } 438 439 updateDisabled(key: string, value: string) { 440 if (this.nav) { 441 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 442 if (item) { 443 if (value) { 444 item.setAttribute('data-disabled', '') 445 } else { 446 item.removeAttribute('data-disabled'); 447 } 448 this.initTabPos() 449 } 450 } 451 } 452 453 updateCloseable(key: string, value: string) { 454 if (this.nav) { 455 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 456 if (item) { 457 if (value) { 458 item.setAttribute('data-closeable', '') 459 } else { 460 item.removeAttribute('data-closeable'); 461 } 462 this.initTabPos() 463 } 464 } 465 } 466 467 updateHidden(key: string, value: string) { 468 if (this.nav) { 469 let item = this.nav.querySelector(`.nav-item[data-key='${key}']`); 470 if (item) { 471 if (value === "true") { 472 item.setAttribute('data-hidden', '') 473 } else { 474 item.removeAttribute('data-hidden'); 475 } 476 this.initTabPos() 477 } 478 } 479 } 480 481 initTabPos() { 482 const items = this.nav!.querySelectorAll<HTMLDivElement>(".nav-item"); 483 Array.from(items).forEach((a, index) => { 484 // @ts-ignore 485 this.tabPos[a.dataset.key] = { 486 index: index, 487 width: a.offsetWidth, 488 height: a.offsetHeight, 489 left: a.offsetLeft, 490 top: a.offsetTop, 491 label: a.textContent 492 } 493 }) 494 if (this.activekey) { 495 if (this.position.startsWith('left')) { 496 this.line?.setAttribute('style', `height:${this.tabPos[this.activekey].height}px;transform:translate(100%,${this.tabPos[this.activekey].top}px)`); 497 } else if (this.position.startsWith('top')) { 498 if (this.tabPos[this.activekey]) { 499 this.line?.setAttribute('style', `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`); 500 } 501 } else if (this.position.startsWith('right')) { 502 this.line?.setAttribute('style', `height:${this.tabPos[this.activekey].height}px;transform:translate(-100%,${this.tabPos[this.activekey].top}px)`); 503 } else if (this.position.startsWith('bottom')) { 504 this.line?.setAttribute('style', `width:${this.tabPos[this.activekey].width}px;transform:translate(${this.tabPos[this.activekey].left}px,100%)`); 505 } 506 } 507 } 508 509 connectedCallback() { 510 let that = this; 511 this.tabPos = {} 512 this.nav = this.shadowRoot?.querySelector('#nav') 513 this.line = this.shadowRoot?.querySelector('#tab-line') 514 this.slots = this.shadowRoot?.querySelector('#slot'); 515 this.slots?.addEventListener('slotchange', () => { 516 const elements: Element[] | undefined = this.slots?.assignedElements(); 517 let panes = this.querySelectorAll<LitTabpane>('lit-tabpane'); 518 if (this.activekey) { 519 panes.forEach(a => { 520 if (a.key === this.activekey) { 521 a.style.display = 'block' 522 } else { 523 a.style.display = 'none'; 524 } 525 }) 526 } else { 527 panes.forEach((a, index) => { 528 if (index === 0) { 529 a.style.display = 'block' 530 this.activekey = a.key || '' 531 } else { 532 a.style.display = 'none'; 533 } 534 }) 535 } 536 537 let navHtml = ""; 538 elements?.map(it => it as LitTabpane).forEach(a => { 539 if (a.disabled) { 540 navHtml += `<div class="nav-item" data-key="${a.key}" data-disabled ${a.closeable ? 'data-closeable' : ''}> 541 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``} 542 <span>${a.tab}</span> 543 <lit-icon class="close-icon" name='close' size="12"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 544 </div>`; 545 } else if (a.hidden) { 546 navHtml += `<div class="nav-item" data-key="${a.key}" data-hidden ${a.closeable ? 'data-closeable' : ''}> 547 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``} 548 <span>${a.tab}</span> 549 <lit-icon class="close-icon" name='close' size="12"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 550 </div>`; 551 } else { 552 if (a.key === this.activekey) { 553 navHtml += `<div class="nav-item" data-key="${a.key}" data-selected ${a.closeable ? 'data-closeable' : ''}> 554 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``} 555 <span>${a.tab}</span> 556 <lit-icon class="close-icon" name='close' size="12"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 557 </div>`; 558 } else { 559 navHtml += `<div class="nav-item" data-key="${a.key}" ${a.closeable ? 'data-closeable' : ''}> 560 ${a.icon ? `<lit-icon name='${a.icon}'></lit-icon>` : ``} 561 <span>${a.tab}</span> 562 <lit-icon class="close-icon" name='close' size="12"></lit-icon><div class="no-close-icon" style="margin-right: 12px"></div> 563 </div>`; 564 } 565 566 } 567 }) 568 this.nav!.innerHTML = navHtml; 569 this.initTabPos() 570 this.nav!.querySelectorAll<HTMLElement>('.close-icon').forEach(a => { 571 a.onclick = (e) => { 572 e.stopPropagation(); 573 const closeKey = (e.target! as HTMLElement).parentElement!.dataset.key; 574 this.dispatchEvent(new CustomEvent('close-handler', {detail: {key: closeKey}, composed: true})); 575 } 576 }); 577 }) 578 this.nav!.onclick = (e) => { 579 if ((e.target! as HTMLElement).closest('div')!.hasAttribute('data-disabled')) return; 580 let key = (e.target! as HTMLElement).closest('div')!.dataset.key; 581 if (key) { 582 this.activeByKey(key) 583 } 584 let label = (e.target! as HTMLElement).closest('div')!.querySelector('span')!.textContent; 585 this.dispatchEvent(new CustomEvent('onTabClick', {detail: {key: key, tab: label}})) 586 }; 587 } 588 589 activeByKey(key: string) { 590 if (key === null || key === undefined) return; 591 this.nav!.querySelectorAll('.nav-item').forEach(a => { 592 if (a.getAttribute('data-key') === key) { 593 a.setAttribute('data-selected', 'true'); 594 } else { 595 a.removeAttribute('data-selected'); 596 } 597 }) 598 let tbp = this.querySelector(`lit-tabpane[key='${key}']`); 599 let panes = this.querySelectorAll<LitTabpane>('lit-tabpane'); 600 panes.forEach(a => { 601 if (a.key === key) { 602 a.style.display = 'block'; 603 this.activekey = a.key; 604 this.initTabPos() 605 } else { 606 a.style.display = 'none'; 607 } 608 }) 609 } 610 611 activePane(key: string) { 612 if (key === null || key === undefined) return false; 613 let tbp = this.querySelector(`lit-tabpane[key='${key}']`); 614 if (tbp) { 615 this.activeByKey(key) 616 return true; 617 } else { 618 return false; 619 } 620 } 621 622 disconnectedCallback() { 623 624 } 625 626 adoptedCallback() { 627 } 628 629 attributeChangedCallback(name: string, oldValue: string, newValue: string) { 630 if (name === 'activekey' && this.nav && oldValue !== newValue && newValue != '') { 631 this.activeByKey(newValue) 632 } 633 } 634} 635