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