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