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-modal') 19export class LitModal extends BaseElement { 20 private headerTitleElement: HTMLElement | null | undefined; 21 private headerElement: HTMLElement | null | undefined; 22 private closeElement: HTMLElement | null | undefined; 23 private cancelElement: HTMLElement | null | undefined; 24 private okElement: HTMLElement | null | undefined; 25 private modalElement: HTMLElement | null | undefined; 26 private resizing: boolean = false; 27 private down: boolean = false; 28 private onmouseleaveMoveFunc: any; 29 private onmouseupFunc: any; 30 private onmousedownMoveFunc: any; 31 private onmousedownFunc: any; 32 private onmousemoveMoveFunc: any; 33 private onmousemoveFunc: any; 34 private onmouseupMoveFunc: any; 35 static get observedAttributes() { 36 return [ 37 'title', //标题 38 'line', //body的线条 39 'visible', //是否显示modal窗口 40 'ok-text', //确定文本 41 'cancel-text', //取消文本 42 'moveable', //设置窗口是否可以鼠标拖动 43 'resizeable', //窗口可改变大小 44 'width', //modal宽度 45 ]; 46 } 47 48 get okText() { 49 return this.getAttribute('ok-text') || '确定'; 50 } 51 52 set okText(value) { 53 this.setAttribute('ok-text', value); 54 } 55 56 get cancelText() { 57 return this.getAttribute('cancel-text') || '取消'; 58 } 59 60 set cancelText(value) { 61 this.setAttribute('cancel-text', value); 62 } 63 64 get title() { 65 return this.getAttribute('title') || ''; 66 } 67 68 set title(value) { 69 this.setAttribute('title', value); 70 } 71 72 get visible() { 73 return this.hasAttribute('visible'); 74 } 75 76 set visible(value) { 77 if (value) { 78 this.setAttribute('visible', ''); 79 } else { 80 this.removeAttribute('visible'); 81 } 82 } 83 84 get width() { 85 return this.getAttribute('width') || '500px'; 86 } 87 88 set width(value) { 89 this.setAttribute('width', value); 90 } 91 92 get resizeable() { 93 return this.hasAttribute('resizeable'); 94 } 95 96 set resizeable(value) { 97 if (value) { 98 this.setAttribute('resizeable', ''); 99 } else { 100 this.removeAttribute('resizeable'); 101 } 102 } 103 104 get moveable() { 105 return this.hasAttribute('moveable'); 106 } 107 108 set moveable(value) { 109 if (value) { 110 this.setAttribute('moveable', ''); 111 } else { 112 this.removeAttribute('moveable'); 113 } 114 } 115 116 set onOk(fn: any) { 117 this.addEventListener('onOk', fn); 118 } 119 120 set onCancel(fn: any) { 121 this.addEventListener('onCancel', fn); 122 } 123 124 initHtml(): string { 125 return ` 126 <style> 127 :host{ 128 display: flex; 129 position: absolute; 130 left: 0; 131 right: 0; 132 top: 0; 133 bottom: 0px; 134 z-index: 1000; 135 overflow: auto; 136 transition: all 0.3s; 137 } 138 :host([visible]){ 139 background-color: #00000066; 140 transition: all .3s; 141 opacity: 1; 142 visibility: visible; 143 } 144 :host(:not([visible])){ 145 pointer-events: none; 146 transition: all .3s; 147 opacity: 0; 148 visibility: hidden; 149 } 150 151 .modal{ 152 width: ${this.width}; 153 position: absolute; 154 display: flex; 155 flex-direction: column; 156 overflow: auto; 157 background-color: #fff; 158 top: 100px; 159 left: 50%; 160 right: auto; 161 transform-origin: left top; 162 pointer-events: all; 163 border-radius: 2px; 164 } 165 :host(:not([visible])) .modal{ 166 transition: transform .3s , opacity .3s,visibility .3s; 167 opacity: 0; 168 transform:scale(0.1) translate(-50%,50%) translateZ(0) skew(0deg); 169 visibility: hidden; 170 } 171 :host([visible]) .modal{ 172 transition: transform .3s , opacity .3s ,visibility .3s; 173 opacity: 1; 174 transform: scale(1) translate(-50%,0%) translateZ(0) skew(0deg); 175 visibility: visible; 176 box-shadow: 0 0 20px #00000055; 177 } 178 .header{ 179 display: flex; 180 align-items: center; 181 justify-content: space-between; 182 padding: 15px 20px; 183 font-size: 17px; 184 font-weight: bold; 185 } 186 :host([moveable]) .header label{ 187 pointer-events: none; 188 } 189 :host([moveable]) .header:hover{ 190 background-color: #8c8c8c11; 191 } 192 :host([moveable]) .header{ 193 /*cursor: move;*/ 194 } 195 .close-icon{ 196 color:#8c8c8c; 197 cursor: pointer; 198 } 199 .close-icon:hover{ 200 color: #414141; 201 } 202 .footer{ 203 display: flex; 204 align-items: center; 205 justify-content: flex-end; 206 padding: 8px 8px; 207 } 208 .footer lit-button{ 209 min-width: 70px; 210 margin-right: 10px; 211 cursor: pointer; 212 } 213 :host([line=false]){ 214 padding: 10px 20px; 215 flex:1; 216 } 217 :host([line=true]) .body, 218 :host(:not([line])) .body{ 219 border-top: 1px solid #f0f0f0; 220 border-bottom: 1px solid #f0f0f0; 221 padding: 10px 20px; 222 flex:1; 223 } 224 225 </style> 226 <div class="modal" title=""> 227 <div class="header"> 228 <label id="modal-title"></label> 229 <lit-icon class="close-icon" name="close"></lit-icon> 230 </div> 231 <div class="body"> 232 <slot></slot> 233 </div> 234 <slot name="footer"> 235 <div class="footer"> 236 <lit-button id="cancel">${this.cancelText}</lit-button> 237 <lit-button id="ok" type="primary">${this.okText}</lit-button> 238 </div> 239 </slot> 240 </div> 241 `; 242 } 243 244 //当 custom element首次被插入文档DOM时,被调用。 245 initElements(): void { 246 this.headerTitleElement = this.shadowRoot!.querySelector<HTMLDivElement>('#modal-title'); 247 this.headerTitleElement!.textContent = this.title; 248 249 this.headerElement = this.shadowRoot!.querySelector<HTMLDivElement>('.header'); 250 this.closeElement = this.shadowRoot!.querySelector<HTMLDivElement>('.close-icon'); 251 this.cancelElement = this.shadowRoot!.querySelector<HTMLDivElement>('#cancel'); 252 this.okElement = this.shadowRoot!.querySelector<HTMLDivElement>('#ok'); 253 this.closeElement!.onclick = (ev) => (this.visible = false); 254 this.modalElement = this.shadowRoot!.querySelector<HTMLDivElement>('.modal'); 255 this.shadowRoot!.querySelector<HTMLDivElement>('.modal')!.onclick = (e) => { 256 e.stopPropagation(); 257 }; 258 this.onclick = (ev) => { 259 ev.stopPropagation(); 260 if (!this.resizeable) { 261 this.visible = false; 262 } 263 }; 264 this.cancelElement!.onclick = (ev) => { 265 this.dispatchEvent(new CustomEvent('onCancel', ev)); 266 }; 267 this.okElement!.onclick = (ev) => { 268 this.dispatchEvent(new CustomEvent('onOk', ev)); 269 }; 270 if (this.moveable || this.resizeable) { 271 if (this.resizeable) { 272 //move 273 let resizeWidth = 8; 274 this.resizing = false; 275 let srcResizeClientX = 0, 276 srcResizeClientY = 0, 277 srcResizeRect, 278 srcResizeHeight = 0, 279 srcResizeWidth = 0, 280 srcResizeRight = 0, 281 srcResizeLeft = 0, 282 srcResizeTop = 0; 283 let direction: string; 284 this.onmousemoveFunc = (e: any) => { 285 e.stopPropagation(); 286 srcResizeRect = this.modalElement!.getBoundingClientRect(); 287 if ( 288 e.clientX > srcResizeRect.left - resizeWidth && 289 e.clientX < srcResizeRect.left + resizeWidth && 290 e.clientY > srcResizeRect.top - resizeWidth && 291 e.clientY < srcResizeRect.top + resizeWidth 292 ) { 293 //left top 294 this.style.cursor = 'nwse-resize'; 295 if (!this.resizing) direction = 'left-top'; 296 } else if ( 297 e.clientX > srcResizeRect.right - resizeWidth && 298 e.clientX < srcResizeRect.right + resizeWidth && 299 e.clientY > srcResizeRect.top - resizeWidth && 300 e.clientY < srcResizeRect.top + resizeWidth 301 ) { 302 //right top 303 this.style.cursor = 'nesw-resize'; 304 if (!this.resizing) direction = 'right-top'; 305 } else if ( 306 e.clientX > srcResizeRect.left - resizeWidth && 307 e.clientX < srcResizeRect.left + resizeWidth && 308 e.clientY > srcResizeRect.bottom - resizeWidth && 309 e.clientY < srcResizeRect.bottom + resizeWidth 310 ) { 311 //left bottom 312 this.style.cursor = 'nesw-resize'; 313 if (!this.resizing) direction = 'left-bottom'; 314 } else if ( 315 e.clientX > srcResizeRect.right - resizeWidth && 316 e.clientX < srcResizeRect.right + resizeWidth && 317 e.clientY > srcResizeRect.bottom - resizeWidth && 318 e.clientY < srcResizeRect.bottom + resizeWidth 319 ) { 320 //right bottom 321 this.style.cursor = 'nwse-resize'; 322 if (!this.resizing) direction = 'right-bottom'; 323 } else if (e.clientX > srcResizeRect.left - resizeWidth && e.clientX < srcResizeRect.left + resizeWidth) { 324 //left 325 this.style.cursor = 'ew-resize'; 326 if (!this.resizing) direction = 'left'; 327 } else if (e.clientX < srcResizeRect.right + resizeWidth && e.clientX > srcResizeRect.right - resizeWidth) { 328 //right 329 this.style.cursor = 'ew-resize'; 330 if (!this.resizing) direction = 'right'; 331 } else if (e.clientY > srcResizeRect.top - resizeWidth && e.clientY < srcResizeRect.top + resizeWidth) { 332 //top 333 this.style.cursor = 'ns-resize'; 334 if (!this.resizing) direction = 'top'; 335 } else if (e.clientY < srcResizeRect.bottom + resizeWidth && e.clientY > srcResizeRect.bottom - resizeWidth) { 336 //bottom 337 this.style.cursor = 'ns-resize'; 338 if (!this.resizing) direction = 'bottom'; 339 } else { 340 this.style.cursor = ''; 341 if (!this.resizing) direction = ''; 342 } 343 if (this.resizing) { 344 let offsetResizeY = e.clientY - srcResizeClientY; 345 let offsetResizeX = e.clientX - srcResizeClientX; 346 if (direction === 'bottom') { 347 this.modalElement!.style.height = srcResizeHeight + offsetResizeY + 'px'; 348 } else if (direction === 'top') { 349 this.modalElement!.style.top = srcResizeTop + offsetResizeY + 'px'; 350 this.modalElement!.style.height = srcResizeHeight - offsetResizeY + 'px'; 351 } else if (direction === 'right') { 352 this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; 353 this.modalElement!.style.width = srcResizeWidth + offsetResizeX + 'px'; 354 } else if (direction === 'left') { 355 this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; 356 this.modalElement!.style.width = srcResizeWidth - offsetResizeX + 'px'; 357 } else if (direction === 'left-top') { 358 this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; 359 this.modalElement!.style.width = srcResizeWidth - offsetResizeX + 'px'; 360 this.modalElement!.style.top = srcResizeTop + offsetResizeY + 'px'; 361 this.modalElement!.style.height = srcResizeHeight - offsetResizeY + 'px'; 362 } else if (direction === 'right-top') { 363 this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; 364 this.modalElement!.style.width = srcResizeWidth + offsetResizeX + 'px'; 365 this.modalElement!.style.top = srcResizeTop + offsetResizeY + 'px'; 366 this.modalElement!.style.height = srcResizeHeight - offsetResizeY + 'px'; 367 } else if (direction === 'left-bottom') { 368 this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; 369 this.modalElement!.style.width = srcResizeWidth - offsetResizeX + 'px'; 370 this.modalElement!.style.height = srcResizeHeight + offsetResizeY + 'px'; 371 } else if (direction === 'right-bottom') { 372 this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; 373 this.modalElement!.style.width = srcResizeWidth + offsetResizeX + 'px'; 374 this.modalElement!.style.height = srcResizeHeight + offsetResizeY + 'px'; 375 } 376 } 377 }; 378 this.onmousedownFunc = (e: any) => { 379 srcResizeRect = this.modalElement!.getBoundingClientRect(); 380 srcResizeClientX = e.clientX; 381 srcResizeClientY = e.clientY; 382 srcResizeHeight = srcResizeRect.height; 383 srcResizeWidth = srcResizeRect.width; 384 srcResizeRight = srcResizeRect.right; 385 srcResizeLeft = srcResizeRect.left; 386 srcResizeTop = srcResizeRect.top; 387 if ( 388 e.clientX > srcResizeRect.left - resizeWidth && 389 e.clientX < srcResizeRect.left + resizeWidth && 390 e.clientY > srcResizeRect.top - resizeWidth && 391 e.clientY < srcResizeRect.top + resizeWidth 392 ) { 393 //left top 394 this.resizing = true; 395 } else if ( 396 e.clientX > srcResizeRect.right - resizeWidth && 397 e.clientX < srcResizeRect.right + resizeWidth && 398 e.clientY > srcResizeRect.top - resizeWidth && 399 e.clientY < srcResizeRect.top + resizeWidth 400 ) { 401 //right top 402 this.resizing = true; 403 } else if ( 404 e.clientX > srcResizeRect.left - resizeWidth && 405 e.clientX < srcResizeRect.left + resizeWidth && 406 e.clientY > srcResizeRect.bottom - resizeWidth && 407 e.clientY < srcResizeRect.bottom + resizeWidth 408 ) { 409 //left bottom 410 this.resizing = true; 411 } else if ( 412 e.clientX > srcResizeRect.right - resizeWidth && 413 e.clientX < srcResizeRect.right + resizeWidth && 414 e.clientY > srcResizeRect.bottom - resizeWidth && 415 e.clientY < srcResizeRect.bottom + resizeWidth 416 ) { 417 //right bottom 418 this.resizing = true; 419 } else if (e.clientX > srcResizeRect.left - resizeWidth && e.clientX < srcResizeRect.left + resizeWidth) { 420 //left 421 this.resizing = true; 422 } else if (e.clientX < srcResizeRect.right + resizeWidth && e.clientX > srcResizeRect.right - resizeWidth) { 423 //right 424 this.resizing = true; 425 } else if (e.clientY > srcResizeRect.top - resizeWidth && e.clientY < srcResizeRect.top + resizeWidth) { 426 //top 427 this.resizing = true; 428 } else if (e.clientY < srcResizeRect.bottom + resizeWidth && e.clientY > srcResizeRect.bottom - resizeWidth) { 429 //bottom 430 this.resizing = true; 431 } else { 432 this.resizing = false; 433 } 434 if (this.resizing) document.body.style.userSelect = 'none'; 435 }; 436 this.onmouseupFunc = (e: any) => { 437 this.resizing = false; 438 }; 439 } 440 441 if (this.moveable) { 442 this.down = false; 443 let srcClientX = 0; 444 let srcClientY = 0; 445 let srcLeft = 0; 446 let srcTop = 0; 447 let srcRight = 0; 448 let srcBottom = 0; 449 let clientRect; 450 let rootRect: any; 451 452 this.onmousedownMoveFunc = (e: any) => { 453 if (this.resizing) return; 454 srcClientX = e.clientX; 455 srcClientY = e.clientY; 456 rootRect = this.getBoundingClientRect(); 457 clientRect = this.modalElement!.getBoundingClientRect(); 458 srcLeft = clientRect.left; 459 srcRight = clientRect.right; 460 srcTop = clientRect.top; 461 srcBottom = clientRect.bottom; 462 if ( 463 e.clientX > srcLeft + 10 && 464 e.clientX < srcRight - 10 && 465 e.clientY > srcTop + 10 && 466 e.clientY < srcTop + this.headerElement!.scrollHeight 467 ) { 468 this.down = true; 469 } else { 470 this.down = false; 471 } 472 if (this.down) document.body.style.userSelect = 'none'; 473 this.onmousemoveMoveFunc = (ev: any) => { 474 if (this.down) { 475 let offsetY = e.clientY - srcClientY; 476 let offsetX = e.clientX - srcClientX; 477 if (e.clientX > srcLeft + 10 && e.clientX < srcRight - 10 && e.clientY > srcTop + 10) { 478 this.headerElement!.style.cursor = 'move'; 479 clientRect = this.modalElement!.getBoundingClientRect(); 480 //下面 rootRect.height 改成 this.scrollHeight 解决modal 过长会出现滚动条的情况 481 if ( 482 ev.clientY - srcClientY + srcTop > 0 && 483 ev.clientY - srcClientY + srcTop < this.scrollHeight - clientRect.height 484 ) { 485 this.modalElement!.style.top = ev.clientY - srcClientY + srcTop + 'px'; 486 } else { 487 if (ev.clientY - srcClientY + srcTop <= 0) { 488 this.modalElement!.style.top = '0px'; 489 } else { 490 //下面 rootRect.height 改成 this.scrollHeight 解决modal 过长会出现滚动条的情况 491 this.modalElement!.style.top = this.scrollHeight - clientRect.height + 'px'; 492 } 493 } 494 //ev.clientX-srcClientX 鼠标移动像素 495 if ( 496 ev.clientX - srcClientX + srcLeft > 0 && 497 ev.clientX - srcClientX + srcLeft < rootRect.width - clientRect.width 498 ) { 499 this.modalElement!.style.left = ev.clientX - srcClientX + srcLeft + clientRect.width / 2 + 'px'; 500 } else { 501 if (ev.clientX - srcClientX + srcLeft <= 0) { 502 this.modalElement!.style.left = clientRect.width / 2 + 'px'; 503 } else { 504 this.modalElement!.style.left = rootRect.width - clientRect.width + clientRect.width / 2 + 'px'; 505 } 506 } 507 } 508 } 509 }; 510 this.onmouseleaveMoveFunc = this.onmouseupMoveFunc = (e: any) => { 511 this.down = false; 512 this.headerElement!.style.cursor = ''; 513 }; 514 }; 515 } 516 this.onmousemove = (e) => { 517 if (this.onmousemoveFunc) this.onmousemoveFunc(e); 518 if (this.onmousemoveMoveFunc) this.onmousemoveMoveFunc(e); 519 }; 520 this.onmousedown = (e) => { 521 if (this.onmousedownFunc) this.onmousedownFunc(e); 522 if (this.onmousedownMoveFunc) this.onmousedownMoveFunc(e); 523 }; 524 this.onmouseleave = this.onmouseup = (e) => { 525 if (this.onmouseleaveMoveFunc) this.onmouseleaveMoveFunc(e); 526 if (this.onmouseupFunc) this.onmouseupFunc(e); 527 document.body.style.userSelect = ''; 528 }; 529 } 530 } 531 532 //当 custom element从文档DOM中删除时,被调用。 533 disconnectedCallback() {} 534 535 //当 custom element被移动到新的文档时,被调用。 536 adoptedCallback() { 537 538 } 539 540 //当 custom element增加、删除、修改自身属性时,被调用。 541 attributeChangedCallback(name: string, oldValue: string, newValue: string) { 542 if (name === 'visible') { 543 if (!newValue && this.modalElement) { 544 this.modalElement.style.top = '100px'; 545 this.modalElement.style.left = '50%'; 546 } 547 } else if (name === 'title' && this.headerTitleElement) { 548 this.headerTitleElement.textContent = newValue; 549 } 550 } 551} 552 553if (!customElements.get('lit-modal')) { 554 customElements.define('lit-modal', LitModal); 555} 556