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