/* * Copyright (C) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { BaseElement, element } from '../BaseElement'; let width = ''; let css = ` ` const initHtmlStyle = (wid: string) => { width = wid; return css; } @element('lit-modal') export class LitModal extends BaseElement { private headerTitleElement: HTMLElement | null | undefined; private headerElement: HTMLElement | null | undefined; private closeElement: HTMLElement | null | undefined; private cancelElement: HTMLElement | null | undefined; private okElement: HTMLElement | null | undefined; private modalElement: HTMLElement | null | undefined; private resizing: boolean = false; private down: boolean = false; private onmouseleaveMoveFunc: any; private onmouseupFunc: any; private onmousedownMoveFunc: any; private onmousedownFunc: any; private onmousemoveMoveFunc: any; private onmousemoveFunc: any; private onmouseupMoveFunc: any; static get observedAttributes() { return [ 'title', //标题 'line', //body的线条 'visible', //是否显示modal窗口 'ok-text', //确定文本 'cancel-text', //取消文本 'moveable', //设置窗口是否可以鼠标拖动 'resizeable', //窗口可改变大小 'width', //modal宽度 ]; } get okText() { return this.getAttribute('ok-text') || '确定'; } set okText(value) { this.setAttribute('ok-text', value); } get cancelText() { return this.getAttribute('cancel-text') || '取消'; } set cancelText(value) { this.setAttribute('cancel-text', value); } get title() { return this.getAttribute('title') || ''; } set title(value) { this.setAttribute('title', value); } get visible() { return this.hasAttribute('visible'); } set visible(value) { if (value) { this.setAttribute('visible', ''); } else { this.removeAttribute('visible'); } } get width() { return this.getAttribute('width') || '500px'; } set width(value) { this.setAttribute('width', value); } get resizeable() { return this.hasAttribute('resizeable'); } set resizeable(value) { if (value) { this.setAttribute('resizeable', ''); } else { this.removeAttribute('resizeable'); } } get moveable() { return this.hasAttribute('moveable'); } set moveable(value) { if (value) { this.setAttribute('moveable', ''); } else { this.removeAttribute('moveable'); } } set onOk(fn: any) { this.addEventListener('onOk', fn); } set onCancel(fn: any) { this.addEventListener('onCancel', fn); } initHtml(): string { return ` ${initHtmlStyle(this.width)} `; } //当 custom element首次被插入文档DOM时,被调用。 initElements(): void { this.headerTitleElement = this.shadowRoot!.querySelector('#modal-title'); this.headerTitleElement!.textContent = this.title; this.headerElement = this.shadowRoot!.querySelector('.header'); this.closeElement = this.shadowRoot!.querySelector('.close-icon'); this.cancelElement = this.shadowRoot!.querySelector('#cancel'); this.okElement = this.shadowRoot!.querySelector('#ok'); this.closeElement!.onclick = (ev) => (this.visible = false); this.modalElement = this.shadowRoot!.querySelector('.modal'); this.shadowRoot!.querySelector('.modal')!.onclick = (e) => { e.stopPropagation(); }; this.setClick(); if (this.moveable || this.resizeable) { if (this.resizeable) { let resizeWidth = 8; this.resizing = false; let srcResizeClientX = 0, srcResizeClientY = 0, srcResizeRect, srcResizeHeight = 0, srcResizeWidth = 0, srcResizeRight = 0, srcResizeLeft = 0, srcResizeTop = 0; let direction: string; this.onmousemoveFunc = (e: any) => { e.stopPropagation(); srcResizeRect = this.modalElement!.getBoundingClientRect(); direction = this.onmousemoveFuncRule(direction,e,srcResizeRect,resizeWidth); this.resizingFunc(direction,e,srcResizeClientX,srcResizeClientY,srcResizeHeight,srcResizeWidth,srcResizeLeft,srcResizeTop); }; this.setOnmousedownFunc(resizeWidth); this.onmouseupFunc = (e: any) => { this.resizing = false; }; } this.buildFunc(); this.onmouseleave = this.onmouseup = (e) => { if (this.onmouseleaveMoveFunc) this.onmouseleaveMoveFunc(e); if (this.onmouseupFunc) this.onmouseupFunc(e); document.body.style.userSelect = ''; }; } } setClick():void{ this.onclick = (ev) => { ev.stopPropagation(); if (!this.resizeable) { this.visible = false; } }; this.cancelElement!.onclick = (ev) => { this.dispatchEvent(new CustomEvent('onCancel', ev)); }; this.okElement!.onclick = (ev) => { this.dispatchEvent(new CustomEvent('onOk', ev)); }; } buildFunc():void{ if (this.moveable) { this.down = false; let srcClientX = 0; let srcClientY = 0; let srcLeft = 0; let srcTop = 0; let srcRight = 0; let srcBottom = 0; let clientRect; let rootRect: any; this.onmousedownMoveFunc = (e: any) => { if (this.resizing) return; srcClientX = e.clientX; srcClientY = e.clientY; rootRect = this.getBoundingClientRect(); clientRect = this.modalElement!.getBoundingClientRect(); srcLeft = clientRect.left; srcRight = clientRect.right; srcTop = clientRect.top; srcBottom = clientRect.bottom; if ( e.clientX > srcLeft + 10 && e.clientX < srcRight - 10 && e.clientY > srcTop + 10 && e.clientY < srcTop + this.headerElement!.scrollHeight ) { this.down = true; } else { this.down = false; } if (this.down) document.body.style.userSelect = 'none'; this.setOnmousemoveMoveFunc(e, srcClientX, srcClientY, srcLeft, srcTop, srcRight, srcBottom, clientRect, rootRect); this.onmouseleaveMoveFunc = this.onmouseupMoveFunc = (e: any) => { this.down = false; this.headerElement!.style.cursor = ''; }; }; } this.onmousemove = (e) => { if (this.onmousemoveFunc) this.onmousemoveFunc(e); if (this.onmousemoveMoveFunc) this.onmousemoveMoveFunc(e); }; this.onmousedown = (e) => { if (this.onmousedownFunc) this.onmousedownFunc(e); if (this.onmousedownMoveFunc) this.onmousedownMoveFunc(e); }; } setOnmousemoveMoveFunc(e:any,srcClientX:number,srcClientY:number,srcLeft:number,srcTop:number,srcRight:number,srcBottom:number,clientRect:any,rootRect:any):void{ this.onmousemoveMoveFunc = (ev: any) => { if (this.down) { let offsetY = e.clientY - srcClientY; let offsetX = e.clientX - srcClientX; if (e.clientX > srcLeft + 10 && e.clientX < srcRight - 10 && e.clientY > srcTop + 10) { this.headerElement!.style.cursor = 'move'; clientRect = this.modalElement!.getBoundingClientRect(); //下面 rootRect.height 改成 this.scrollHeight 解决modal 过长会出现滚动条的情况 if ( ev.clientY - srcClientY + srcTop > 0 && ev.clientY - srcClientY + srcTop < this.scrollHeight - clientRect.height ) { this.modalElement!.style.top = ev.clientY - srcClientY + srcTop + 'px'; } else { if (ev.clientY - srcClientY + srcTop <= 0) { this.modalElement!.style.top = '0px'; } else { //下面 rootRect.height 改成 this.scrollHeight 解决modal 过长会出现滚动条的情况 this.modalElement!.style.top = this.scrollHeight - clientRect.height + 'px'; } } //ev.clientX-srcClientX 鼠标移动像素 if ( ev.clientX - srcClientX + srcLeft > 0 && ev.clientX - srcClientX + srcLeft < rootRect.width - clientRect.width ) { this.modalElement!.style.left = ev.clientX - srcClientX + srcLeft + clientRect.width / 2 + 'px'; } else { if (ev.clientX - srcClientX + srcLeft <= 0) { this.modalElement!.style.left = clientRect.width / 2 + 'px'; } else { this.modalElement!.style.left = rootRect.width - clientRect.width + clientRect.width / 2 + 'px'; } } } } }; } onmousemoveFuncRule(direction:string,e:any,srcResizeRect:any,resizeWidth:number):string{ if ( e.clientX > srcResizeRect.left - resizeWidth && e.clientX < srcResizeRect.left + resizeWidth && e.clientY > srcResizeRect.top - resizeWidth && e.clientY < srcResizeRect.top + resizeWidth ) { this.style.cursor = 'nwse-resize'; if (!this.resizing) return 'left-top'; } else if ( e.clientX > srcResizeRect.right - resizeWidth && e.clientX < srcResizeRect.right + resizeWidth && e.clientY > srcResizeRect.top - resizeWidth && e.clientY < srcResizeRect.top + resizeWidth ) { this.style.cursor = 'nesw-resize'; if (!this.resizing) return 'right-top'; } else if ( e.clientX > srcResizeRect.left - resizeWidth && e.clientX < srcResizeRect.left + resizeWidth && e.clientY > srcResizeRect.bottom - resizeWidth && e.clientY < srcResizeRect.bottom + resizeWidth ) { this.style.cursor = 'nesw-resize'; if (!this.resizing) return 'left-bottom'; } else { return this.funcRuleIf(direction,e,srcResizeRect,resizeWidth); } return '' } funcRuleIf(direction:string,e:any,srcResizeRect:any,resizeWidth:number):string{ if ( e.clientX > srcResizeRect.right - resizeWidth && e.clientX < srcResizeRect.right + resizeWidth && e.clientY > srcResizeRect.bottom - resizeWidth && e.clientY < srcResizeRect.bottom + resizeWidth ) { this.style.cursor = 'nwse-resize'; if (!this.resizing) return 'right-bottom'; } else if (e.clientX > srcResizeRect.left - resizeWidth && e.clientX < srcResizeRect.left + resizeWidth) { this.style.cursor = 'ew-resize'; if (!this.resizing) return 'left'; } else if (e.clientX < srcResizeRect.right + resizeWidth && e.clientX > srcResizeRect.right - resizeWidth) { this.style.cursor = 'ew-resize'; if (!this.resizing) return 'right'; } else if (e.clientY > srcResizeRect.top - resizeWidth && e.clientY < srcResizeRect.top + resizeWidth) { this.style.cursor = 'ns-resize'; if (!this.resizing) return 'top'; } else if (e.clientY < srcResizeRect.bottom + resizeWidth && e.clientY > srcResizeRect.bottom - resizeWidth) { this.style.cursor = 'ns-resize'; if (!this.resizing) return 'bottom'; } else { this.style.cursor = ''; if (!this.resizing) return ''; } return ''; } resizingFunc(direction:string,e:any,srcResizeClientX:number,srcResizeClientY:number,srcResizeHeight:number,srcResizeWidth:number,srcResizeLeft:number,srcResizeTop:number):void{ if (this.resizing) { let offsetResizeY = e.clientY - srcResizeClientY; let offsetResizeX = e.clientX - srcResizeClientX; if (direction === 'bottom') { this.modalElement!.style.height = srcResizeHeight + offsetResizeY + 'px'; } else if (direction === 'top') { this.modalElement!.style.top = srcResizeTop + offsetResizeY + 'px'; this.modalElement!.style.height = srcResizeHeight - offsetResizeY + 'px'; } else if (direction === 'right') { this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; this.modalElement!.style.width = srcResizeWidth + offsetResizeX + 'px'; } else if (direction === 'left') { this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; this.modalElement!.style.width = srcResizeWidth - offsetResizeX + 'px'; } else if (direction === 'left-top') { this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; this.modalElement!.style.width = srcResizeWidth - offsetResizeX + 'px'; this.modalElement!.style.top = srcResizeTop + offsetResizeY + 'px'; this.modalElement!.style.height = srcResizeHeight - offsetResizeY + 'px'; } else if (direction === 'right-top') { this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; this.modalElement!.style.width = srcResizeWidth + offsetResizeX + 'px'; this.modalElement!.style.top = srcResizeTop + offsetResizeY + 'px'; this.modalElement!.style.height = srcResizeHeight - offsetResizeY + 'px'; } else if (direction === 'left-bottom') { this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; this.modalElement!.style.width = srcResizeWidth - offsetResizeX + 'px'; this.modalElement!.style.height = srcResizeHeight + offsetResizeY + 'px'; } else if (direction === 'right-bottom') { this.modalElement!.style.left = srcResizeLeft + srcResizeWidth / 2 + offsetResizeX / 2 + 'px'; this.modalElement!.style.width = srcResizeWidth + offsetResizeX + 'px'; this.modalElement!.style.height = srcResizeHeight + offsetResizeY + 'px'; } } } setOnmousedownFunc(resizeWidth: number): void { this.onmousedownFunc = (e: any) => { const srcResizeRect = this.modalElement!.getBoundingClientRect(); const { clientX, clientY } = e; const { left, right, top, bottom, width, height } = srcResizeRect; const resizeRange = resizeWidth * 2; const isWithinRange = (coord: number, target: number, range: number) => coord > target - range && coord < target + range; const isWithinCornerRange = (cornerX: number, cornerY: number) => isWithinRange(clientX, cornerX, resizeRange) && isWithinRange(clientY, cornerY, resizeRange); const isWithinTopLeft = isWithinCornerRange(left, top); const isWithinTopRight = isWithinCornerRange(right, top); const isWithinBottomLeft = isWithinCornerRange(left, bottom); const isWithinBottomRight = isWithinCornerRange(right, bottom); if ( isWithinTopLeft || isWithinTopRight || isWithinBottomLeft || isWithinBottomRight ) { this.resizing = true; } else { this.resizeIf(e, srcResizeRect, resizeWidth); } if (this.resizing) { document.body.style.userSelect = 'none'; } }; } resizeIf(e:any,srcResizeRect:any,resizeWidth:number){ if (e.clientX > srcResizeRect.left - resizeWidth && e.clientX < srcResizeRect.left + resizeWidth) { this.resizing = true; } else if (e.clientX < srcResizeRect.right + resizeWidth && e.clientX > srcResizeRect.right - resizeWidth) { this.resizing = true; } else if (e.clientY > srcResizeRect.top - resizeWidth && e.clientY < srcResizeRect.top + resizeWidth) { this.resizing = true; } else if (e.clientY < srcResizeRect.bottom + resizeWidth && e.clientY > srcResizeRect.bottom - resizeWidth) { this.resizing = true; } else { this.resizing = false; } } //当 custom element从文档DOM中删除时,被调用。 disconnectedCallback() {} //当 custom element被移动到新的文档时,被调用。 adoptedCallback() {} //当 custom element增加、删除、修改自身属性时,被调用。 attributeChangedCallback(name: string, oldValue: string, newValue: string) { if (name === 'visible') { if (!newValue && this.modalElement) { this.modalElement.style.top = '100px'; this.modalElement.style.left = '50%'; } } else if (name === 'title' && this.headerTitleElement) { this.headerTitleElement.textContent = newValue; } } } if (!customElements.get('lit-modal')) { customElements.define('lit-modal', LitModal); }