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