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