TASK: #110347 实现overlay与overflowmenu组件的融合

This commit is contained in:
yajun 2022-09-24 13:45:33 +08:00
parent 71999f0f26
commit a6b17562ee
8 changed files with 283 additions and 290 deletions

View File

@ -1,6 +1,7 @@
import {LitElement, html, HTMLTemplateResult, CSSResultArray} from 'lit'
import {customElement, property, queryAssignedElements} from 'lit/decorators.js'
import '../button/button'
import {OverlayStack} from '../overlay/overlay-stack'
import {sharedStyles} from './overflowmenustyle'
@customElement('star-overflowmenu')
@ -20,81 +21,36 @@ export class StarOverflowMenu extends LitElement {
@queryAssignedElements({flatten: true})
_evenEl: any
_getElement() {
// 获取网页宽度用于判断菜单显示位置是否越界
const bodywidth = document.documentElement.clientWidth
const bodyheight = document.documentElement.clientHeight
// 获取菜单所在div,用于控制menu显示或隐藏ts默认使用Element需转换为HTMLElement
const mu = this.renderRoot.querySelector('#menuitem') as HTMLElement
// 获取star-button相对屏幕的位置
const buttonposition = this.renderRoot
.querySelector('star-button')
?.getBoundingClientRect()
// star-button的top、bottom、left及right值
const buttontop = Number(buttonposition?.top)
const buttonbottom = Number(buttonposition?.bottom)
const buttonleft = Number(buttonposition?.left)
const buttonright = Number(buttonposition?.right)
// 通过“open”判断是否显示menu
if (this.open == true) {
for (var i = 0; i < this._evenEl.length; i++) {
const slotelement = this._evenEl[i]
// 设置div显示display状态
mu.style.display = 'block'
// 设置显示位置类型
// this._evenEl[i].style.position = 'fixed'
slotelement.style.position = 'relative'
this.open = false
// 获取溢出菜单width及height
const menuwidth = slotelement.getBoundingClientRect().width
const menuheight = slotelement.getBoundingClientRect().height
// 弹出菜单边界,rightline和bottomline分别为是否超过右侧和下侧显示区域
const rightline = buttonright + menuwidth > bodywidth ? true : false
const bottomline = buttonbottom + menuheight > bodyheight ? true : false
// 右下角边界
if (rightline && bottomline) {
slotelement.style.left =
-(menuwidth - (buttonright - buttonleft)) + 'px'
slotelement.style.bottom =
menuheight + (buttonbottom - buttontop) + 'px'
return
} else if (rightline) {
// 右侧边界
slotelement.style.right =
menuwidth - (buttonright - buttonleft) + 'px'
return
} else if (bottomline) {
// 下侧边界
slotelement.style.bottom =
menuheight + (buttonbottom - buttontop) + 'px'
return
} else {
// 正常情况
return
}
}
public overlayStack = new OverlayStack()
public showmenu(e: Event) {
if (this.overlayStack.isOpen == false) {
// 获取响应事件节点
const targetNode = e.target as HTMLElement
// 获取溢出菜单
const originalNode = this
// 获取overlay显示内容
const overlaycontent = this.querySelector('#menu') as HTMLElement
// overlaycontent为空则退出
if (!overlaycontent) return
// 开启overlay
this.overlayStack.openOverlay(originalNode, targetNode, overlaycontent)
} else {
for (var i = 0; i < this._evenEl.length; i++) {
mu.style.display = 'none'
this.open = true
}
this.overlayStack.closeOverlay()
}
}
protected firstUpdated(): void {
this._getElement()
}
getBaseMenu(): HTMLTemplateResult {
// 为OverflowMenu绑定监听
this.addEventListener('test', () => {
this.overlayStack.closeOverlay()
})
return html`
<star-button
type="icononly"
icon=${this.icon}
@click=${this._getElement}
id="openmenu"
@click=${this.showmenu}
></star-button>
<div id="menuitem">
<slot></slot>
</div>
`
}

View File

@ -2,22 +2,19 @@ import {css, CSSResult} from 'lit'
export const sharedStyles: CSSResult = css`
:host {
width: auto;
width: inherit;
max-width: 300px;
display: block;
text-align: left;
}
#menuitem {
width: auto;
position: inherit;
}
star-button {
display: block;
width: 35px;
}
::slotted(star-ul) {
display: none;
margin: 0;
z-index: 2;
}

View File

@ -1,10 +1,15 @@
import {LitElement, html, TemplateResult} from 'lit'
import {LitElement, html, TemplateResult, CSSResultArray} from 'lit'
import {customElement, property} from 'lit/decorators.js'
import {OverlayOpenDetail, TriggerInteractions} from './overlay-types'
import {OverlayOpenDetail} from './overlay-types'
import { sharedStyles } from './overlaystyle'
@customElement('star-activeoverlay')
export class ActiveOverlay extends LitElement {
public static override get styles(): CSSResultArray {
return [sharedStyles]
}
// 根节点(占位符所表示的元素的父节点)
public root?: HTMLElement
// 被点击的节点
@ -26,12 +31,11 @@ export class ActiveOverlay extends LitElement {
// 初始化函数
private extractDetail(detail: OverlayOpenDetail): void {
this.placement = detail.placement
this.offset = detail.offset
this.skidding = detail.skidding || 0
this.root = detail.root
}
private stealOverlayContent(element: HTMLElement) {}
// 位置更新函数
public updatePosition = () => {
// 设置最大显示宽度和高度
@ -45,7 +49,7 @@ export class ActiveOverlay extends LitElement {
const bodyheight = document.documentElement.clientHeight
// 被点击的节点
const targetnode = this.targetNode as HTMLElement
// 获取被点击节点位置——用于越界判断和定位
// 获取被点击节点位置——用于越界判断和定位:分别表示上下左右四个方位
const targetNodePosition = targetnode.getBoundingClientRect()
const targettop = Number(targetNodePosition?.top)
const targetbottom = Number(targetNodePosition?.bottom)
@ -53,17 +57,17 @@ export class ActiveOverlay extends LitElement {
const targetright = Number(targetNodePosition?.right)
// 设置样式
this.style.position = 'relative'
this.style.zIndex = '1'
this.style.zIndex = '10'
this.style.maxWidth = availableWidth + 'px'
this.style.maxHeight = availableHeight + 'px'
// 边界判断
// 边界判断targetright指被点击的节点右边界contentwidth表示将要显示的内容的宽度
const rightline = targetright + contentwidth > bodywidth ? true : false // 右侧越界条件
const bottomline =
targetbottom + availableHeight > bodyheight ? true : false //下方越界条件
// 右下角边界
if (rightline && bottomline) {
this.style.left = targetleft - (contentwidth - targetnode.offsetWidth) + 'px'
this.style.top = targettop - targetnode.offsetHeight + 'px'
this.style.top = targettop - contentheight - targetnode.offsetHeight + 'px'
return
} else if (rightline) {
// 右侧边界
@ -73,7 +77,7 @@ export class ActiveOverlay extends LitElement {
} else if (bottomline) {
// 下侧边界
this.style.left = targetleft + 'px'
this.style.top = targettop - contentheight + 'px'
this.style.top = targettop - contentheight - targetnode.offsetHeight + 'px'
return
} else {
// 正常情况

View File

@ -9,18 +9,46 @@ export class OverlayStack {
const activeOverlay = ActiveOverlay.create(root, targetnode, content)
// 开启状态
this.isOpen = true
// 为overlay添加显示内容
activeOverlay.appendChild(content as HTMLElement)
// 创建注释节点模板——用于替换要展示在overlay中的元素
const placeholderTemplate: Comment = document.createComment(
'placeholder for reparented element'
)
// 为overlay添加显示内容
activeOverlay.appendChild(content as HTMLElement)
// 占位
activeOverlay.root?.appendChild(placeholderTemplate)
// 将activeoverlay添加到body底部
document.body.append(activeOverlay)
// // 将activeoverlay添加到body底部显示div中
let showmenu = document.querySelector('#showmenu') as HTMLElement //后续关联模态时
if(showmenu==null){
showmenu = document.createElement('div')
showmenu.setAttribute('id','showmenu')
Object.assign(showmenu.style, {
width: '100vw',
height: '100vh',
position: 'fixed',
backgroundColor: '#00000033',
justifycontent: 'center',
});
}else{
showmenu.style.display = 'fixed'
}
// 为显示div添加overlay并将div添加到body中
showmenu.append(activeOverlay)
document.body.appendChild(showmenu)
// 将activeoverlay添加到已打开overlay数组中
this.overlays.push(activeOverlay)
// 为showmenu显示层添加监听
showmenu.addEventListener('click',function(e:Event){
console.log(e.target);
if(e.target == this){
root!.dispatchEvent(
new Event('test', {
bubbles: true,
composed: true,
})
)
}
})
}
/**
@ -35,10 +63,14 @@ export class OverlayStack {
const placeholder = srcroot?.lastChild as Node
// 获取overlay中的内容
const content = closeactiveoverlay.restoreContent as HTMLElement
console.log(content.offsetWidth);
// 替换子节点
srcroot?.replaceChild(content, placeholder)
const showmenu = document.querySelector('#showmenu') as HTMLElement
// 从body中移除activeoverlay节点
document.body.removeChild(closeactiveoverlay)
showmenu.removeChild(closeactiveoverlay)
// showmenu.style.display = 'none'
document.body.removeChild(showmenu)
this.isOpen = false
}
}

View File

@ -1,83 +0,0 @@
import { OverlayStack } from './overlay-stack'
import { OverlayOptions, TriggerInteractions } from './overlay-types'
export interface OverlayDisplayQueryDetail {
overlayRootName?: string
overlayRootElement?: HTMLElement
overlayContentTipElement?: HTMLElement
}
export class Overlay {
// 基础属性
private isOpen = false
private overlayElement: HTMLElement
private owner: HTMLElement
private interaction: TriggerInteractions
private static overlayStack = new OverlayStack()
/**
*
* @param owner overlay显示位置的父元素节点
* @param interaction overlay显示的交互类型
* @param overlayElement overlay的节点
*/
constructor(
owner: HTMLElement,
interaction: TriggerInteractions,
overlayElement: HTMLElement
) {
this.owner = owner
this.overlayElement = overlayElement
this.interaction = interaction
}
public static async open(
owner: HTMLElement,
interaction: TriggerInteractions,
overlayElement: HTMLElement,
options: OverlayOptions
): Promise<() => void> {
const overlay = new Overlay(owner, interaction, overlayElement)
await overlay.open(options)
return (): void => {
overlay.close()
}
}
public static update(): void {
// 自定义事件
const overlayUpdateEvent = new CustomEvent('star-update-overlays', {
bubbles: true,
composed: true,
cancelable: true,
})
// 触发事件
document.dispatchEvent(overlayUpdateEvent)
}
public async open({
offset = 0,
placement = 'top',
root,
}: OverlayOptions): Promise<boolean> {
/* c8 ignore next */
if (this.isOpen) return true
await Overlay.overlayStack.openOverlay({
content: this.overlayElement,
offset: offset,
placement: placement,
trigger: this.owner,
interaction: this.interaction,
root,
})
this.isOpen = true
return true
}
// 关闭overlay
public close(): void {
Overlay.overlayStack.closeOverlay(this.overlayElement)
}
}

View File

@ -0,0 +1,9 @@
import {css, CSSResult} from 'lit'
export const sharedStyles: CSSResult = css`
:host {
display: block;
}
div {
max-width: 200px;
}
`

View File

@ -40,7 +40,7 @@ export class PanelActiveOverlay extends LitElement {
<button></button>
</div>
<div style="position: fixed; top: 98%;">
<div style="position: fixed; top: 20%;">
<button id="open" @click="${this.test}">overlay层</button>
<button></button>
</div>
@ -50,7 +50,7 @@ export class PanelActiveOverlay extends LitElement {
<button></button>
</div>
<div style="position: fixed; top: 98%; left: 90%;">
<div style="position: fixed; top: 80%; left: 90%;">
<button id="open" @click="${this.test}">overlay层</button>
<button></button>
</div>

View File

@ -1,10 +1,9 @@
import {html, LitElement, css} from 'lit'
import {html, LitElement, css, CSSResultArray} from 'lit'
import {customElement, property} from 'lit/decorators.js'
import '../../../components/button/button'
import '../../../components/ul/ul'
import '../../../components/li//li'
import {UlType} from '../../../components/ul/ul'
import {LiType} from '../../../components/li//li'
import {baseStyles} from '../../../components/base/base-style'
@customElement('panel-overflowmenu')
export class PanelOverflowMenu extends LitElement {
@ -12,134 +11,213 @@ export class PanelOverflowMenu extends LitElement {
super()
}
// state用于记录展开的菜单数量用以菜单展开状态互斥的判断
@property({type: Number}) state = 0
// 关闭菜单
closeoverflowmenu(e: any) {
// 获取点击事件所在的标签名字
const tagName = e.target.tagName.toLowerCase()
// 判断是否点击的star-overflowmenu标签
if (tagName == 'star-overflowmenu') {
this.state++
}
// 如果点在空白处则关闭菜单
if (tagName != 'star-overflowmenu') {
// 获取所有的star-overflowmenu
var menulist = this.shadowRoot!.querySelectorAll('star-overflowmenu')
for (var i = 0; i < menulist.length; i++) {
menulist[i].open = true
var menu = menulist[i].renderRoot.querySelector(
'#menuitem'
) as HTMLElement
menu.style.display = 'none'
this.state = 0
}
}
// 通过state判断是否已有展开的菜单若已有则关闭菜单
if (this.state > 1) {
var menulist = this.shadowRoot!.querySelectorAll('star-overflowmenu')
for (var i = 0; i < menulist.length; i++) {
menulist[i].open = true
var menu = menulist[i].renderRoot.querySelector(
'#menuitem'
) as HTMLElement
menu.style.display = 'none'
this.state = 0
}
}
}
connectedCallback(): void {
super.connectedCallback()
// 添加click事件
this.shadowRoot?.addEventListener('click', (e) => this.closeoverflowmenu(e))
}
render() {
return html`
<div>
<star-overflowmenu icon="more">
<star-ul type=${UlType.BASE}>
<star-li
type=${LiType.ONLY_EDIT}
label="星光麒麟"
default="星光麒麟"
></star-li>
</star-ul>
</star-overflowmenu>
<star-overflowmenu
icon="more"
style="position: fixed; top: 50%; left: 50%;"
>
<star-ul type=${UlType.BASE}>
<star-li
type=${LiType.ONLY_EDIT}
label="星光麒麟"
default="星光麒麟"
></star-li>
</star-ul>
</star-overflowmenu>
<star-overflowmenu
icon="more"
style="position: fixed; top: 0; right: 0;"
>
<star-ul type=${UlType.BASE}>
<star-li
type=${LiType.ONLY_EDIT}
label="星光麒麟"
default="星光麒麟"
></star-li>
<star-li
type=${LiType.ONLY_EDIT}
label="星光麒麟"
default="星光麒麟"
></star-li>
</star-ul>
</star-overflowmenu>
<star-overflowmenu
icon="more"
style="position: fixed; bottom: 0; left: 0;"
>
<star-overflowmenu icon="more">
<div id="menu">
<star-ul
type=${UlType.ONLY_HEADER}
title="头部有文字"
text="尾部有文字"
type="onlyheader"
title="菜单"
id="iconmenu"
background="var(--bg-dialog)"
>
<star-li type=${LiType.ONLY_LABEL} label="素条目"></star-li>
<star-button
type="iconlabel"
icon="info"
label="应用详情"
></star-button>
<star-button
type="iconlabel"
icon="delete"
label="卸载"
></star-button>
<star-button
type="iconlabel"
icon="download-circle"
label="固定至Dock"
></star-button>
</star-ul>
</star-overflowmenu>
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
<star-button
type="iconlabel"
icon="keyboard-circle"
label="编辑屏幕"
></star-button>
</star-ul>
</div>
</star-overflowmenu>
<star-overflowmenu
icon="more"
style="position: fixed; bottom: 0; right: 0;"
>
<star-overflowmenu
icon="more"
style="position: fixed; top: 50%; left: 50%;"
>
<div id="menu">
<star-ul
type=${UlType.ONLY_HEADER}
title="头部有文字"
text="尾部有文字"
type="onlyheader"
title="菜单"
id="iconmenu"
background="var(--bg-dialog)"
>
<star-li type=${LiType.ONLY_LABEL} label="素条目"></star-li>
<star-button
type="iconlabel"
icon="info"
label="应用详情"
></star-button>
<star-button
type="iconlabel"
icon="delete"
label="卸载"
></star-button>
<star-button
type="iconlabel"
icon="download-circle"
label="固定至Dock"
></star-button>
</star-ul>
</star-overflowmenu>
</div>
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
<star-button
type="iconlabel"
icon="keyboard-circle"
label="编辑屏幕"
></star-button>
</star-ul>
</div>
</star-overflowmenu>
<star-overflowmenu icon="more" style="position: fixed; top: 0; left: 90%;">
<div id="menu">
<star-ul
type="onlyheader"
title="菜单"
id="iconmenu"
background="var(--bg-dialog)"
>
<star-button
type="iconlabel"
icon="info"
label="应用详情"
></star-button>
<star-button
type="iconlabel"
icon="delete"
label="卸载"
></star-button>
<star-button
type="iconlabel"
icon="download-circle"
label="固定至Dock"
></star-button>
</star-ul>
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
<star-button
type="iconlabel"
icon="keyboard-circle"
label="编辑屏幕"
></star-button>
</star-ul>
</div>
</star-overflowmenu>
<star-overflowmenu
icon="more"
style="position: fixed; top: 90%; left: 0;"
>
<div id="menu">
<star-ul
type="onlyheader"
title="菜单"
id="iconmenu"
background="var(--bg-dialog)"
>
<star-button
type="iconlabel"
icon="info"
label="应用详情"
></star-button>
<star-button
type="iconlabel"
icon="delete"
label="卸载"
></star-button>
<star-button
type="iconlabel"
icon="download-circle"
label="固定至Dock"
></star-button>
</star-ul>
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
<star-button
type="iconlabel"
icon="keyboard-circle"
label="编辑屏幕"
></star-button>
</star-ul>
</div>
</star-overflowmenu>
<star-overflowmenu
icon="more"
style="position: fixed; top: 90%; left: 90%;"
>
<div id="menu">
<star-ul
type="onlyheader"
title="菜单"
id="iconmenu"
background="var(--bg-dialog)"
>
<star-button
type="iconlabel"
icon="info"
label="应用详情"
></star-button>
<star-button
type="iconlabel"
icon="delete"
label="卸载"
></star-button>
<star-button
type="iconlabel"
icon="download-circle"
label="固定至Dock"
></star-button>
</star-ul>
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
<star-button
type="iconlabel"
icon="keyboard-circle"
label="编辑屏幕"
></star-button>
</star-ul>
</div>
</star-overflowmenu>
`
}
static styles = css`
:host {
display: block;
width: 100vw;
height: 100vh;
}
div {
display: block;
width: 100vw;
height: 100vh;
}
`
static get styles(): CSSResultArray {
return [
baseStyles,
css`
star-ul#dialog {
border-radius: var(--base-dialog-radius);
}
star-ul#menu {
max-width: 200px;
border-radius: var(--base-menu-radius);
}
star-ul#iconmenu {
max-width: 150px;
border-radius: var(--base-menu-radius);
}
star-ul#dialog {
border-radius: var(--base-menu-radius);
}
star-li span.split {
color: var(--split-line);
}
`,
]
}
}
declare global {