• 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 {BaseElement, element} from "../BaseElement.js";
17
18@element('lit-select')
19export class LitSelect extends BaseElement {
20    private focused: any;
21    private inputElement: any;
22    private clearElement: any;
23    private iconElement: any;
24    private searchElement: any;
25    private multipleRootElement: any;
26
27    static get observedAttributes() {
28        return [
29            'value',
30            'default-value',
31            'placeholder',
32            'disabled',
33            'loading',
34            'allow-clear',
35            'show-search',
36            'list-height',
37            'border',
38            'mode',
39        ];
40    }
41
42    get value() {
43        return this.getAttribute('value') || this.defaultValue;
44    }
45
46    set value(value) {
47        this.setAttribute('value', value);
48    }
49
50    get rounded() {
51        return this.hasAttribute("rounded");
52    }
53
54    set rounded(rounded: boolean) {
55        if (rounded) {
56            this.setAttribute("rounded", '');
57        } else {
58            this.removeAttribute("rounded");
59        }
60    }
61
62    get placement(): string {
63        return this.getAttribute("placement") || "";
64    }
65
66    set placement(placement: string) {
67        if (placement) {
68            this.setAttribute("placement", placement);
69        } else {
70            this.removeAttribute("placement");
71        }
72    }
73
74    get border() {
75        return this.getAttribute('border') || 'true';
76    }
77
78    set border(value) {
79        if (value) {
80            this.setAttribute('border', 'true');
81        } else {
82            this.setAttribute('border', 'false');
83        }
84    }
85
86    get listHeight() {
87        return this.getAttribute('list-height') || '256px';
88    }
89
90    set listHeight(value) {
91        this.setAttribute('list-height', value);
92    }
93
94    get defaultPlaceholder() {
95        return this.getAttribute('placeholder') || '请选择';
96    }
97
98    set canInsert(can:boolean) {
99        if (can) {
100            this.setAttribute("canInsert",'');
101        } else {
102            this.removeAttribute("canInsert")
103        }
104    }
105
106    get canInsert() {
107        return this.hasAttribute("canInsert")
108    }
109    get showSearch() {
110        return this.hasAttribute('show-search');
111    }
112
113    get defaultValue() {
114        return this.getAttribute('default-value') || '';
115    }
116
117    set defaultValue(value) {
118        this.setAttribute('default-value', value);
119    }
120
121    get placeholder() {
122        return this.getAttribute('placeholder') || this.defaultPlaceholder;
123    }
124
125    set placeholder(value) {
126        this.setAttribute('placeholder', value);
127    }
128
129    get loading() {
130        return this.hasAttribute('loading');
131    }
132
133    set loading(value) {
134        if (value) {
135            this.setAttribute('loading', '');
136        } else {
137            this.removeAttribute('loading')
138        }
139    }
140
141    set dataSource(value: any) {
142        value.forEach((a: any) => {
143            let option = document.createElement('lit-select-option');
144            option.setAttribute('value', a.key);
145            option.textContent = a.val;
146            this.append(option)
147        })
148        this.initOptions();
149    }
150
151    initElements(): void {
152    }
153
154    initHtml() {
155        return `
156        <style>
157        :host{
158            display: inline-flex;
159            position: relative;
160            overflow: visible;
161            cursor: pointer;
162            border-radius: 2px;
163            outline: none;
164            -webkit-user-select:none ;
165            -moz-user-select:none;
166            user-select:none;
167            /*width: 100%;*/
168        }
169        :host(:not([border])),
170        :host([border='true']){
171            border: 1px solid var(--bark-prompt,#dcdcdc);
172        }
173        input{
174            border: 0;
175            outline: none;
176            background-color: transparent;
177            cursor: pointer;
178            -webkit-user-select:none ;
179            -moz-user-select:none;
180            user-select:none;
181            display: inline-flex;
182            color: var(--dark-color2,rgba(0,0,0,0.9));
183        }
184        :host([highlight]) input {
185            color: rgba(255,255,255,0.9);
186        }
187        :host(:not([mode]))  input{
188            width: 100%;
189        }
190        :host([mode])  input{
191            padding: 6px 0px;
192        }
193        :host([mode])  .root{
194            padding: 1px 8px;
195        }
196        .root{
197            position: relative;
198            padding: 3px 6px;
199            display: flex;
200            align-items: center;
201            justify-content: space-between;
202            border-radius: 2px;
203            outline: none;
204            font-size: 1rem;
205            z-index: 2;
206            -webkit-user-select:none ;
207            -moz-user-select:none;
208            user-select:none;
209            width: 100%;
210        }
211        .body{
212            max-height: ${this.listHeight};
213            position: absolute;
214            bottom: 100%;
215            z-index: 99;
216            padding-top: 5px;
217            margin-top: 2px;
218            background-color: var(--dark-background4,#fff);
219            width: 100%;
220            transform: scaleY(.6);
221            visibility: hidden;
222            opacity: 0;
223            transform-origin: bottom center;
224            display: block;
225            flex-direction: column;
226            box-shadow: 0 5px 15px 0px #00000033;
227            border-radius: 2px;
228            overflow: auto;
229        }
230        .body-bottom{
231            bottom: auto;
232            top: 100%;
233            transform-origin: top center;
234        }
235        :host([placement="bottom"]) .body{
236            bottom:unset;
237            top: 100%;
238            transition: none;
239            transform: none;
240        }
241
242        :host([rounded]) .body {
243            border-radius: 16px;
244        }
245        :host([rounded]) .root {
246            border-radius: 16px;
247            height: 25px;
248        }
249        .icon{
250            pointer-events: none;
251        }
252        .noSelect{
253          -moz-user-select:none;
254          -ms-user-select:none;
255          user-select:none;
256          -khtml-user-select:none;
257          -webkit-touch-callout:none;
258          -webkit-user-select:none;
259        }
260
261        :host(:not([border]):not([disabled]):focus),
262        :host([border='true']:not([disabled]):focus),
263        :host(:not([border]):not([disabled]):hover),
264        :host([border='true']:not([disabled]):hover){
265            border:1px solid var(--bark-prompt,#ccc)
266        }
267        :host(:not([disabled]):focus) .body,
268        :host(:not([disabled]):focus-within) .body{
269            transform: scaleY(1);
270            opacity: 1;
271            z-index: 99;
272            visibility: visible;
273        }
274        :host(:not([disabled]):focus)  input{
275            color: var(--dark-color,#bebebe);
276        }
277        .multipleRoot input::-webkit-input-placeholder {
278                color: var(--dark-color,#aab2bd);
279            }
280        :host(:not([border])[disabled]) *,
281        :host([border='true'][disabled]) *{
282            background-color: var(--dark-background1,#f5f5f5);
283            color: #b7b7b7;
284            cursor: not-allowed;
285        }
286        :host([border='false'][disabled]) *{
287            color: #b7b7b7;
288            cursor: not-allowed;
289        }
290        :host([loading]) .loading{
291            display: flex;
292        }
293        :host([loading]) .icon{
294            display: none;
295        }
296        :host(:not([loading])) .loading{
297            display: none;
298        }
299        :host(:not([loading])) .icon{
300            display: flex;
301        }
302        :host(:not([allow-clear])) .clear{
303            display: none;
304        }
305        .clear{
306            display: none;
307            color: #bfbfbf;
308        }
309        .clear:hover{
310            color: #8c8c8c;
311        }
312        .search{
313            display: none;
314            color: #bfbfbf;
315        }
316        .multipleRoot{
317            display: flex;
318            flex-direction: column;
319            flex-wrap: wrap;
320            flex-flow: wrap;
321            align-items: center;
322        }
323        .tag{
324            display: inline-flex;
325            align-items: center;
326            background-color: #f5f5f5;
327            padding: 1px 4px;
328            height: auto;
329            font-size: .75rem;
330            font-weight: bold;
331            color: #242424;
332            overflow: auto;
333            position: relative;
334            margin-right: 4px;
335            margin-top: 1px;
336            margin-bottom: 1px;
337        }
338        .tag-close{
339            font-size: .8rem;
340            padding: 2px;
341            margin-left: 0px;
342            color: #999999;
343        }
344        .tag-close:hover{
345            color: #333;
346        }
347
348        </style>
349        <div class="root noSelect" tabindex="0" hidefocus="true">
350            <div class="multipleRoot">
351            <input placeholder="${this.placeholder}" autocomplete="off" ${this.showSearch || this.canInsert ? '' : 'readonly'} tabindex="0"></div>
352            <lit-loading class="loading" size="12"></lit-loading>
353            <lit-icon class="icon" name='down' color="#c3c3c3"></lit-icon>
354            <lit-icon class="clear" name='close-circle-fill'></lit-icon>
355            <lit-icon class="search" name='search'></lit-icon>
356        </div>
357        <div class="body">
358            <slot></slot>
359            <slot name="footer"></slot>
360        </div>
361        `
362    }
363
364    isMultiple() {
365        return this.hasAttribute('mode') && this.getAttribute('mode') === 'multiple'
366    }
367
368    newTag(value: any, text: any) {
369        let tag: any = document.createElement('div');
370        let icon: any = document.createElement('lit-icon');
371        icon.classList.add('tag-close')
372        icon.name = 'close'
373        let span = document.createElement('span');
374        tag.classList.add('tag');
375        span.dataset['value'] = value;
376        span.textContent = text;
377        tag.append(span);
378        tag.append(icon);
379        icon.onclick = (ev: any) => {
380            tag.parentElement.removeChild(tag);
381            this.querySelector(`lit-select-option[value=${value}]`)!.removeAttribute('selected')
382            if (this.shadowRoot!.querySelectorAll('.tag').length == 0) {
383                this.inputElement.style.width = 'auto';
384                this.inputElement.placeholder = this.defaultPlaceholder;
385            }
386            ev.stopPropagation();
387        }
388        tag.value = value;
389        tag.dataset['value'] = value;
390        tag.text = text;
391        tag.dataset['text'] = text;
392        return tag;
393    }
394
395    connectedCallback() {
396        this.tabIndex = 0;
397        this.focused = false;
398        this.inputElement = this.shadowRoot!.querySelector('input');
399        this.clearElement = this.shadowRoot!.querySelector('.clear');
400        this.iconElement = this.shadowRoot!.querySelector('.icon');
401        this.searchElement = this.shadowRoot!.querySelector('.search');
402        this.multipleRootElement = this.shadowRoot!.querySelector('.multipleRoot');
403        this.clearElement.onclick = (ev: any) => {
404            if (this.isMultiple()) {
405                let delNodes: Array<any> = []
406                this.multipleRootElement.childNodes.forEach((a: any) => {
407                    if (a.tagName === 'DIV') {
408                        delNodes.push(a);
409                    }
410                })
411                for (let i = 0; i < delNodes.length; i++) {
412                    delNodes[i].remove();
413                }
414                if (this.shadowRoot!.querySelectorAll('.tag').length == 0) {
415                    this.inputElement.style.width = 'auto';
416                    this.inputElement.placeholder = this.defaultPlaceholder;
417                }
418            }
419            this.querySelectorAll('lit-select-option').forEach(a => a.removeAttribute('selected'));
420            this.inputElement.value = ''
421            this.clearElement.style.display = 'none';
422            this.iconElement.style.display = 'flex';
423            this.blur();
424            ev.stopPropagation();
425            this.dispatchEvent(new CustomEvent('onClear', {detail: ev}))
426        }
427        this.initOptions();
428        this.onclick = (ev: any) => {
429            if (ev.target.tagName === 'LIT-SELECT') {
430                if (this.focused === false) {
431                    this.inputElement.focus();
432                    this.focused = true;
433                } else {
434                    this.blur();
435                    this.focused = false;
436                }
437            }
438        }
439        this.onmouseover = this.onfocus = ev => {
440            if (this.focused === false && this.hasAttribute("adaptive-expansion")) {
441                let body = this.shadowRoot!.querySelector(".body");
442                if (this.parentElement!.offsetTop < body!.clientHeight) {
443                    body!.classList.add('body-bottom');
444                } else {
445                    body!.classList.remove('body-bottom');
446                }
447            }
448            if (this.hasAttribute('allow-clear')) {
449                if (this.inputElement.value.length > 0 || this.inputElement.placeholder !== this.defaultPlaceholder) {
450                    this.clearElement.style.display = 'flex'
451                    this.iconElement.style.display = 'none';
452                } else {
453                    this.clearElement.style.display = 'none'
454                    this.iconElement.style.display = 'flex';
455                }
456            }
457        }
458        this.onmouseout = this.onblur = ev => {
459            if (this.hasAttribute('allow-clear')) {
460                this.clearElement.style.display = 'none';
461                this.iconElement.style.display = 'flex';
462            }
463            this.focused = false;
464        }
465        this.inputElement.onfocus = (ev: any) => {
466            if (this.hasAttribute('disabled')) return;
467            if (this.inputElement.value.length > 0) {
468                this.inputElement.placeholder = this.inputElement.value;
469                this.inputElement.value = ''
470            }
471            if (this.hasAttribute('show-search')) {
472                this.searchElement.style.display = 'flex';
473                this.iconElement.style.display = 'none';
474            }
475            this.querySelectorAll('lit-select-option').forEach(a => {
476                // @ts-ignore
477                a.style.display = 'flex';
478            })
479        }
480        this.inputElement.onblur = (ev: any) => {
481            if (this.hasAttribute('disabled')) return;
482            if (this.isMultiple()) {
483                if (this.hasAttribute('show-search')) {
484                    this.searchElement.style.display = 'none';
485                    this.iconElement.style.display = 'flex';
486                }
487            } else {
488                if (this.inputElement.placeholder !== this.defaultPlaceholder) {
489                    this.inputElement.value = this.inputElement.placeholder;
490                    this.inputElement.placeholder = this.defaultPlaceholder;
491                }
492                if (this.hasAttribute('show-search')) {
493                    this.searchElement.style.display = 'none';
494                    this.iconElement.style.display = 'flex';
495                }
496            }
497        }
498        this.inputElement.oninput = (ev: any) => {
499            let els = [...this.querySelectorAll('lit-select-option')];
500            if(this.hasAttribute("show-search")) {
501                if (!ev.target.value) {
502                    els.forEach((a: any) => a.style.display = 'flex');
503                } else {
504                    els.forEach((a: any) => {
505                        let value = a.getAttribute('value');
506                        if (value.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1 ||
507                            a.textContent.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1) {
508                            a.style.display = 'flex';
509                        } else {
510                            a.style.display = 'none';
511                        }
512                    })
513                }
514            } else {
515                this.value = ev.target.value
516            }
517        }
518        this.inputElement.onkeydown = (ev: any) => {
519            if (ev.key === 'Backspace') {
520                if (this.isMultiple()) {
521                    let tag = this.multipleRootElement.lastElementChild.previousElementSibling;
522                    if (tag) {
523                        this.querySelector(`lit-select-option[value=${tag.value}]`)?.removeAttribute('selected');
524                        tag.remove()
525                        if (this.shadowRoot!.querySelectorAll('.tag').length == 0) {
526                            this.inputElement.style.width = 'auto';
527                            this.inputElement.placeholder = this.defaultPlaceholder;
528                        }
529                    }
530                } else {
531                    this.clear();
532                    this.dispatchEvent(new CustomEvent('onClear', {detail: ev}))//向外派发清理事件
533                }
534            } else if (ev.key === 'Enter') {
535                if (!this.canInsert) {
536                    let filter = [...this.querySelectorAll('lit-select-option')].filter((a: any) => a.style.display !== 'none');
537                    if (filter.length > 0) {
538                        this.inputElement.value = filter[0].textContent;
539                        this.inputElement.placeholder = filter[0].textContent;
540                        this.blur();
541                        // @ts-ignore
542                        this.value = filter[0].getAttribute('value')
543                        this.dispatchEvent(new CustomEvent('change', {
544                            detail: {
545                                selected: true,
546                                value: filter[0].getAttribute('value'),
547                                text: filter[0].textContent
548                            }
549                        }));
550                    }
551                }
552            }
553        }
554    }
555
556    initOptions() {
557        this.querySelectorAll('lit-select-option').forEach(a => {
558            if (this.isMultiple()) {
559                a.setAttribute('check', '');
560                if (a.getAttribute('value') === this.defaultValue) {
561                    let tag = this.newTag(a.getAttribute('value'), a.textContent);
562                    this.multipleRootElement.insertBefore(tag, this.inputElement);
563                    this.inputElement.placeholder = '';
564                    this.inputElement.value = '';
565                    this.inputElement.style.width = '1px';
566                    a.setAttribute('selected', '');
567                }
568            } else {
569                if (a.getAttribute('value') === this.defaultValue) {
570                    this.inputElement.value = a.textContent;
571                    a.setAttribute('selected', '');
572                }
573            }
574            a.addEventListener('mouseup',(e)=>{
575               e.stopPropagation()
576            });
577            a.addEventListener('mousedown',(e)=>{
578                e.stopPropagation()
579            });
580            a.addEventListener('onSelected', (e: any) => {
581                if (this.isMultiple()) {
582                    if (a.hasAttribute('selected')) {
583                        let tag = this.shadowRoot!.querySelector(`div[data-value=${e.detail.value}]`) as HTMLElement;
584                        if (tag)  {
585                            tag.parentElement!.removeChild(tag);
586                        }
587                        e.detail.selected = false;
588                    } else {
589                        let tag = this.newTag(e.detail.value, e.detail.text);
590                        this.multipleRootElement.insertBefore(tag, this.inputElement);
591                        this.inputElement.placeholder = '';
592                        this.inputElement.value = '';
593                        this.inputElement.style.width = '1px';
594                    }
595                    if (this.shadowRoot!.querySelectorAll('.tag').length == 0) {
596                        this.inputElement.style.width = 'auto';
597                        this.inputElement.placeholder = this.defaultPlaceholder;
598                    }
599                    this.inputElement.focus();
600                } else {
601                    [...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected'))
602                    this.blur();
603                    // @ts-ignore
604                    this.inputElement.value = e.detail.text;
605                }
606                if (a.hasAttribute('selected')) {
607                    a.removeAttribute('selected')
608                } else {
609                    a.setAttribute('selected', '')
610                }
611                // @ts-ignore
612                this.value = e.detail.value;
613                this.dispatchEvent(new CustomEvent('change', {detail: e.detail}));//向外层派发change事件,返回当前选中项
614            })
615        })
616    }
617
618    clear() {
619        this.inputElement.value = '';
620        this.inputElement.placeholder = this.defaultPlaceholder;
621    }
622
623    reset() {
624        this.querySelectorAll('lit-select-option').forEach(a => {
625            [...this.querySelectorAll('lit-select-option')].forEach(a => a.removeAttribute('selected'))
626            if (a.getAttribute('value') === this.defaultValue) {
627                this.inputElement.value = a.textContent;
628                a.setAttribute('selected', '');
629            }
630        })
631    }
632
633    disconnectedCallback() {
634
635    }
636
637    adoptedCallback() {
638    }
639
640    attributeChangedCallback(name: any, oldValue: any, newValue: any) {
641        if (name === 'value' && this.inputElement) {
642            if (newValue) {
643                [...this.querySelectorAll('lit-select-option')].forEach(a => {
644                    if (a.getAttribute('value') === newValue) {
645                        a.setAttribute('selected', '');
646                        this.inputElement.value = a.textContent;
647                    } else {
648                        a.removeAttribute('selected')
649                    }
650                })
651            } else {
652                this.clear();
653            }
654        }
655    }
656
657}
658