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