• 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';
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