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'; 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 ${selectHtmlStr(this.listHeight)} 160 <div class="root noSelect" tabindex="0" hidefocus="true"> 161 <div class="multipleRoot"> 162 <input placeholder="${this.placeholder}" autocomplete="off" ${ 163 this.showSearch || this.canInsert ? '' : 'readonly' 164 } tabindex="0"></div> 165 <lit-loading class="loading" size="12"></lit-loading> 166 <lit-icon class="icon" name='down' color="#c3c3c3"></lit-icon> 167 <lit-icon class="clear" name='close-circle-fill'></lit-icon> 168 <lit-icon class="search" name='search'></lit-icon> 169 </div> 170 <div class="body"> 171 <slot></slot> 172 <slot name="footer"></slot> 173 </div> 174 `; 175 } 176 177 isMultiple() { 178 return this.hasAttribute('mode') && this.getAttribute('mode') === 'multiple'; 179 } 180 181 newTag(value: any, text: any) { 182 let tag: any = document.createElement('div'); 183 let icon: any = document.createElement('lit-icon'); 184 icon.classList.add('tag-close'); 185 icon.name = 'close'; 186 let span = document.createElement('span'); 187 tag.classList.add('tag'); 188 span.dataset['value'] = value; 189 span.textContent = text; 190 tag.append(span); 191 tag.append(icon); 192 icon.onclick = (ev: any) => { 193 tag.parentElement.removeChild(tag); 194 this.querySelector(`lit-select-option[value=${value}]`)!.removeAttribute('selected'); 195 if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { 196 this.selectInputEl.style.width = 'auto'; 197 this.selectInputEl.placeholder = this.defaultPlaceholder; 198 } 199 ev.stopPropagation(); 200 }; 201 tag.value = value; 202 tag.dataset['value'] = value; 203 tag.text = text; 204 tag.dataset['text'] = text; 205 return tag; 206 } 207 208 connectedCallback() { 209 this.tabIndex = 0; 210 this.focused = false; 211 this.bodyEl = this.shadowRoot!.querySelector('.body'); 212 this.selectInputEl = this.shadowRoot!.querySelector('input'); 213 this.selectClearEl = this.shadowRoot!.querySelector('.clear'); 214 this.selectIconEl = this.shadowRoot!.querySelector('.icon'); 215 this.selectSearchEl = this.shadowRoot!.querySelector('.search'); 216 this.selectMultipleRootEl = this.shadowRoot!.querySelector('.multipleRoot'); 217 this.setEventClick(); 218 this.setEvent(); 219 this.selectInputEl.onblur = (ev: any) => { 220 if (this.hasAttribute('disabled')) return; 221 if (this.isMultiple()) { 222 if (this.hasAttribute('show-search')) { 223 this.selectSearchEl.style.display = 'none'; 224 this.selectIconEl.style.display = 'flex'; 225 } 226 } else { 227 if (this.selectInputEl.placeholder !== this.defaultPlaceholder) { 228 this.selectInputEl.value = this.selectInputEl.placeholder; 229 this.selectInputEl.placeholder = this.defaultPlaceholder; 230 } 231 if (this.hasAttribute('show-search')) { 232 this.selectSearchEl.style.display = 'none'; 233 this.selectIconEl.style.display = 'flex'; 234 } 235 } 236 }; 237 this.setOninput(); 238 this.setOnkeydown(); 239 } 240 241 setOninput():void{ 242 this.selectInputEl.oninput = (ev: any) => { 243 let els: Element[] = [...this.querySelectorAll('lit-select-option')]; 244 if (this.hasAttribute('show-search')) { 245 if (!ev.target.value) { 246 els.forEach((a: any) => (a.style.display = 'flex')); 247 } else { 248 this.setSelectItem(els,ev) 249 } 250 } else { 251 this.value = ev.target.value; 252 } 253 }; 254 } 255 256 setSelectItem(els:Element[],ev:any):void{ 257 els.forEach((a: any) => { 258 let value = a.getAttribute('value'); 259 if ( 260 value.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1 || 261 a.textContent.toLowerCase().indexOf(ev.target.value.toLowerCase()) !== -1 262 ) { 263 a.style.display = 'flex'; 264 } else { 265 a.style.display = 'none'; 266 } 267 }); 268 } 269 270 setEventClick():void{ 271 this.selectClearEl.onclick = (ev: any) => { 272 if (this.isMultiple()) { 273 let delNodes: Array<any> = []; 274 this.selectMultipleRootEl.childNodes.forEach((a: any) => { 275 if (a.tagName === 'DIV') { 276 delNodes.push(a); 277 } 278 }); 279 for (let i = 0; i < delNodes.length; i++) { 280 delNodes[i].remove(); 281 } 282 if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { 283 this.selectInputEl.style.width = 'auto'; 284 this.selectInputEl.placeholder = this.defaultPlaceholder; 285 } 286 } 287 this.querySelectorAll('lit-select-option').forEach((a) => a.removeAttribute('selected')); 288 this.selectInputEl.value = ''; 289 this.selectClearEl.style.display = 'none'; 290 this.selectIconEl.style.display = 'flex'; 291 this.blur(); 292 ev.stopPropagation(); 293 this.dispatchEvent(new CustomEvent('onClear', { detail: ev })); 294 }; 295 this.initOptions(); 296 this.onclick = (ev: any) => { 297 if (ev.target.tagName === 'LIT-SELECT') { 298 if (this.focused === false) { 299 this.selectInputEl.focus(); 300 this.focused = true; 301 this.bodyEl!.style.display = 'block'; 302 } else { 303 this.blur(); 304 this.bodyEl!.style.display = 'none'; 305 this.focused = false; 306 } 307 } 308 }; 309 } 310 311 setEvent():void{ 312 this.onmouseover = this.onfocus = (ev) => { 313 if (this.focused === false && this.hasAttribute('adaptive-expansion')) { 314 if (this.parentElement!.offsetTop < this.bodyEl!.clientHeight) { 315 this.bodyEl!.classList.add('body-bottom'); 316 } else { 317 this.bodyEl!.classList.remove('body-bottom'); 318 } 319 } 320 if (this.hasAttribute('allow-clear')) { 321 if (this.selectInputEl.value.length > 0 || this.selectInputEl.placeholder !== this.defaultPlaceholder) { 322 this.selectClearEl.style.display = 'flex'; 323 this.selectIconEl.style.display = 'none'; 324 } else { 325 this.selectClearEl.style.display = 'none'; 326 this.selectIconEl.style.display = 'flex'; 327 } 328 } 329 }; 330 this.onmouseout = this.onblur = (ev) => { 331 if (this.hasAttribute('allow-clear')) { 332 this.selectClearEl.style.display = 'none'; 333 this.selectIconEl.style.display = 'flex'; 334 } 335 this.focused = false; 336 }; 337 this.selectInputEl.onfocus = (ev: any) => { 338 if (this.hasAttribute('disabled')) return; 339 if (this.selectInputEl.value.length > 0) { 340 this.selectInputEl.placeholder = this.selectInputEl.value; 341 this.selectInputEl.value = ''; 342 } 343 if (this.hasAttribute('show-search')) { 344 this.selectSearchEl.style.display = 'flex'; 345 this.selectIconEl.style.display = 'none'; 346 } 347 this.querySelectorAll('lit-select-option').forEach((a) => { 348 // @ts-ignore 349 a.style.display = 'flex'; 350 }); 351 }; 352 } 353 354 setOnkeydown():void{ 355 this.selectInputEl.onkeydown = (ev: any) => { 356 if (ev.key === 'Backspace') { 357 if (this.isMultiple()) { 358 let tag = this.selectMultipleRootEl.lastElementChild.previousElementSibling; 359 if (tag) { 360 this.querySelector(`lit-select-option[value=${tag.value}]`)?.removeAttribute('selected'); 361 tag.remove(); 362 if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { 363 this.selectInputEl.style.width = 'auto'; 364 this.selectInputEl.placeholder = this.defaultPlaceholder; 365 } 366 } 367 } else { 368 this.clear(); 369 this.dispatchEvent(new CustomEvent('onClear', { detail: ev })); //向外派发清理事件 370 } 371 } else if (ev.key === 'Enter') { 372 if (!this.canInsert) { 373 let filter = [...this.querySelectorAll('lit-select-option')].filter((a: any) => a.style.display !== 'none'); 374 if (filter.length > 0) { 375 this.selectInputEl.value = filter[0].textContent; 376 this.selectInputEl.placeholder = filter[0].textContent; 377 this.blur(); 378 // @ts-ignore 379 this.value = filter[0].getAttribute('value'); 380 this.dispatchEvent( 381 new CustomEvent('change', { 382 detail: { 383 selected: true, 384 value: filter[0].getAttribute('value'), 385 text: filter[0].textContent, 386 }, 387 }) 388 ); 389 } 390 } 391 } else if (ev.key === '0' && ev.target.value.length === 1 && ev.target.value === '0') { 392 ev.preventDefault(); 393 } 394 }; 395 } 396 397 initOptions() { 398 this.querySelectorAll('lit-select-option').forEach((a) => { 399 if (this.isMultiple()) { 400 a.setAttribute('check', ''); 401 if (a.getAttribute('value') === this.defaultValue) { 402 let tag = this.newTag(a.getAttribute('value'), a.textContent); 403 this.selectMultipleRootEl.insertBefore(tag, this.selectInputEl); 404 this.selectInputEl.placeholder = ''; 405 this.selectInputEl.value = ''; 406 this.selectInputEl.style.width = '1px'; 407 a.setAttribute('selected', ''); 408 } 409 } else { 410 if (a.getAttribute('value') === this.defaultValue) { 411 this.selectInputEl.value = a.textContent; 412 a.setAttribute('selected', ''); 413 } 414 } 415 a.addEventListener('mouseup', (e) => { 416 e.stopPropagation(); 417 }); 418 a.addEventListener('mousedown', (e) => { 419 e.stopPropagation(); 420 }); 421 this.onSelectedEvent(a); 422 }); 423 } 424 425 onSelectedEvent(a:Element):void{ 426 a.addEventListener('onSelected', (e: any) => { 427 if (this.isMultiple()) { 428 if (a.hasAttribute('selected')) { 429 let tag = this.shadowRoot!.querySelector(`div[data-value=${e.detail.value}]`) as HTMLElement; 430 if (tag) { 431 tag.parentElement!.removeChild(tag); 432 } 433 e.detail.selected = false; 434 } else { 435 let tag = this.newTag(e.detail.value, e.detail.text); 436 this.selectMultipleRootEl.insertBefore(tag, this.selectInputEl); 437 this.selectInputEl.placeholder = ''; 438 this.selectInputEl.value = ''; 439 this.selectInputEl.style.width = '1px'; 440 } 441 if (this.shadowRoot!.querySelectorAll('.tag').length == 0) { 442 this.selectInputEl.style.width = 'auto'; 443 this.selectInputEl.placeholder = this.defaultPlaceholder; 444 } 445 this.selectInputEl.focus(); 446 } else { 447 [...this.querySelectorAll('lit-select-option')].forEach((a) => a.removeAttribute('selected')); 448 this.blur(); 449 this.bodyEl!.style.display = 'none'; 450 // @ts-ignore 451 this.selectInputEl.value = e.detail.text; 452 } 453 if (a.hasAttribute('selected')) { 454 a.removeAttribute('selected'); 455 } else { 456 a.setAttribute('selected', ''); 457 } 458 // @ts-ignore 459 this.value = e.detail.value; 460 this.dispatchEvent(new CustomEvent('change', { detail: e.detail })); //向外层派发change事件,返回当前选中项 461 }); 462 } 463 464 clear() { 465 this.selectInputEl.value = ''; 466 this.selectInputEl.placeholder = this.defaultPlaceholder; 467 } 468 469 reset() { 470 this.querySelectorAll('lit-select-option').forEach((a) => { 471 [...this.querySelectorAll('lit-select-option')].forEach((a) => a.removeAttribute('selected')); 472 if (a.getAttribute('value') === this.defaultValue) { 473 this.selectInputEl.value = a.textContent; 474 a.setAttribute('selected', ''); 475 } 476 }); 477 } 478 479 disconnectedCallback() {} 480 481 adoptedCallback() {} 482 483 attributeChangedCallback(name: any, oldValue: any, newValue: any) { 484 if (name === 'value' && this.selectInputEl) { 485 if (newValue) { 486 [...this.querySelectorAll('lit-select-option')].forEach((a) => { 487 if (a.getAttribute('value') === newValue) { 488 a.setAttribute('selected', ''); 489 this.selectInputEl.value = a.textContent; 490 } else { 491 a.removeAttribute('selected'); 492 } 493 }); 494 } else { 495 this.clear(); 496 } 497 } 498 } 499} 500