• 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            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