• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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