Merge branch 'master' into feature-component-digicipher

This commit is contained in:
wangchangqi 2022-09-22 16:55:13 +08:00
commit 95f46ab5f3
9 changed files with 506 additions and 29 deletions

View File

@ -3,28 +3,43 @@
## 介绍
### overflowmenu溢出菜单。
现有overflowmenu包含一个绑定点击事件的star-button按钮通过在响应函数closeoverflowmenu控制溢出菜单内容的显示与隐藏。
后续改进结合active-overlay组件通过调用overlay-stack类控制溢出菜单的显示与隐藏
## 新需求(主页面要求——罗 9.5
1. 外部颜色控制(思路:使用自定义 css 样式,如: --test-colorXXX p{color --test-color},通过修改自定义 css 样式的值达到从外部修改组件颜色)
2. 弹出菜单时的越界判断,包括主、副屏切换时的图标定位以及旋转屏幕时的定位
思路:
1首先获取 button 在屏幕显示的 left、right、top 和 bottom 值以及 menu 的 width 和 height
2对于右侧边界right >= width ? true 则 menu 的 left = button 的 left : false 则 menu 的 right = button 的 right
3对于下边界bottom >= height ? true 则 menu 的 top = button 的 bottom false 则 menu 的 bottom = button 的 top
3. 外部控制接口,事件还是属性(暂定)
4. 弹出的菜单绑定在父节点上以供调用,减少重复使用(思路:后续通过 overlay 组件实现)
## 问题(9.6)
1. 首次点击最右侧的按钮是获取到的菜单宽度和高度与实际不符:(该问题已消失,但不知道为何消失)
2. 点击空白处无法关闭菜单栏(解决方法:将点击事件绑定在父容器中)
## 新要求:(9.7)
1将不需要修改的“var”变量声明变成“const”(已修改)
2变量命名要直观且有解释已修改变量命名规范并添加对应注释
3点击一个按钮后其余按钮应关闭方法同1
4可以将 slot 增加名称从而将 div 以删除
5定位方式修改为相对定位将将 fixed 改为 relative达到适应效果
6控制菜单栏宽度菜单栏中 star-ul 中的 ul 标签负责扩充大小,修改其 width 值
```typescript
//现有方案
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
}
}
}
```

View File

@ -1,5 +1,76 @@
# 全局 overlay
# Adobe SP组件库——overlay
## 类型包括:
介绍Adobe的overlay组件是一个可以用于全局调用的叠加层。
- 置于底部的 overlay内可填充 ul
## 交互类型
- type TriggerInteractions =
| 'click'
| 'custom',
| 'hover'
| 'modal'
overlay可有多种触发类型各类型对应情况如下
>click将打开一个overlay该overlay将在下一次单击时立即关闭该overlay不在overlay内的元素上。
>custom允许从外部对显示流程进行定制。
>hover一旦指针离开overlay层所连接的触发器就会关闭overlay层。
>modal模态形式将仅在其内容中捕获标签顺序。
## overlay中主要用到的方法
```typescript
Overlay.open(
(owner: HTMLElement),
(interaction: TriggerInteractions),
(overlayElement: HTMLElement),
(options: OverlayOptions) //
);
```
Overlay.open() 是一个异步方法它返回一个用于关闭overlay的函数。
>owner:HTMLElement 代表要打开叠加层的元素
>interaction:TriggerInteractions overlay的触发交互类型
>overlayElement:HTMLElement 将要放入overlay中的element元素
>options:OverlayOptions overlay定制选项
## 作用
- 置于底部的overlay内可填充ul,其他组件可通过调用overlay显示信息
# 组件交互
- 可与button、menu、popover、picker等联合使用
# overlay场景、应有接口
- 弹出框、溢出菜单、picker、警告框、主页面应用图标长按显示信息框等
- 接口position显示位置top、right、left、bottom、interaction type交互方式click、custom、hover、modal、replace、overlayoptions显示配置delay延迟出现overlayplacement自定义位置offset偏移量
# overlay包含内容
> overlay.ts工厂类包含异步函数open()其通过调用open()和close()函数实现overlay的打开和关闭
>> open(OverlayOptions)其中OverlayOptions为overlay的一些配置参数open应有方法两个主要方法updatePosition()和reparentChildren()
>>> updatePosition()方法实现open过程的overlay显示位置定位
>>> reparentChildren()方法实现组件中content和overlay显示位置的转换
>> close()关闭overlay主要分为两步关闭overlay将overlay的content替换回原位置
>>> 移除overlayremoveoverlay节点
>>> reparentChildren替换元素
# overlay使用
- 首先获取响应点击事件的节点元素:
```typescript
const targetNode = e.target as HTMLElement
```
- 获取被点击节点父节点:
```typescript
const originalNode = targetNode?.parentElement as HTMLElement
```
- 获取要显示在overlay中的内容
```typescript
const content = targetNode?.nextElementSibling as HTMLElement
```
- 构造响应函数时通过调用overlayStack中的openOverlay方法创建activeoverlauy
```typescript
this.overlayStack.openOverlay(originalNode, targetNode, content)
```

View File

@ -0,0 +1,119 @@
import {LitElement, html, TemplateResult} from 'lit'
import {customElement, property} from 'lit/decorators.js'
import {OverlayOpenDetail, TriggerInteractions} from './overlay-types'
@customElement('star-activeoverlay')
export class ActiveOverlay extends LitElement {
// 根节点(占位符所表示的元素的父节点)
public root?: HTMLElement
// 被点击的节点
public targetNode?: HTMLElement
// 被替换后用以保存要复原的节点集合
public restoreContent?: HTMLElement
// 显示位置策略
@property({reflect: true})
public placement?: String
public constructor() {
super()
}
// 调用初始化函数
private open(openDetail: OverlayOpenDetail): void {
this.extractDetail(openDetail)
}
// 初始化函数
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 = () => {
// 设置最大显示宽度和高度
const availableWidth = 200
const availableHeight = 200
// 显示内容content宽度和高度——用于越界后的定位计算右下、右、下
const contentwidth = this.restoreContent?.offsetWidth as number
const contentheight = this.restoreContent?.offsetHeight as number
// 获取网页宽度——用于越界判断
const bodywidth = document.documentElement.clientWidth
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)
const targetleft = Number(targetNodePosition?.left)
const targetright = Number(targetNodePosition?.right)
// 设置样式
this.style.position = 'relative'
this.style.zIndex = '1'
this.style.maxWidth = availableWidth + 'px'
this.style.maxHeight = availableHeight + 'px'
// 边界判断
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'
return
} else if (rightline) {
// 右侧边界
this.style.left = targetleft - (contentwidth - targetnode.offsetWidth) + 'px'
this.style.top = targettop + targetnode.offsetHeight + 'px'
return
} else if (bottomline) {
// 下侧边界
this.style.left = targetleft + 'px'
this.style.top = targettop - contentheight + 'px'
return
} else {
// 正常情况
this.style.left = targetleft + 'px'
this.style.top = targettop + targetnode.offsetHeight + 'px'
return
}
}
private onSlotChange(): void {
this.updatePosition()
}
public render(): TemplateResult {
const content = html`
<div>
<slot @slotchange=${this.onSlotChange}></slot>
</div>
`
return content
}
// 创建overlay
public static create(
root?: HTMLElement,
targetnode?: HTMLElement,
content?: HTMLElement
): ActiveOverlay {
const activeoverlay = new ActiveOverlay()
// 初始化参数
activeoverlay.root = root
activeoverlay.targetNode = targetnode
activeoverlay.restoreContent = content
return activeoverlay
}
}
declare global {
interface HTMLElementTagNameMap {
'star-activeoverlay': ActiveOverlay
}
}

View File

@ -0,0 +1,44 @@
import {ActiveOverlay} from './active-overlay'
export class OverlayStack {
public overlays: ActiveOverlay[] = []
public isOpen: Boolean = false
public openOverlay(root?: HTMLElement, targetnode?: HTMLElement, content?: HTMLElement) {
// 创建activeoverlay对象
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'
)
// 占位
activeOverlay.root?.appendChild(placeholderTemplate)
// 将activeoverlay添加到body底部
document.body.append(activeOverlay)
// 将activeoverlay添加到已打开overlay数组中
this.overlays.push(activeOverlay)
}
/**
* closeOverlay
*/
public closeOverlay(): void {
// 提取要关闭的overlay
const closeactiveoverlay = this.overlays.pop() as unknown as ActiveOverlay
// 获取根节点
const srcroot = closeactiveoverlay.root
// 获取注释节点
const placeholder = srcroot?.lastChild as Node
// 获取overlay中的内容
const content = closeactiveoverlay.restoreContent as HTMLElement
// 替换子节点
srcroot?.replaceChild(content, placeholder)
// 从body中移除activeoverlay节点
document.body.removeChild(closeactiveoverlay)
this.isOpen = false
}
}

View File

@ -0,0 +1,58 @@
// 定义overlay各种参数类型、接口
// click用于普通弹窗longpress可用于主界面图标长按显示详情页hover可用于解释框的显示
// custom、inline、replace和modal尚未理解用途暂定
export type TriggerInteractions =
| 'click'
| 'longpress'
| 'hover'
| 'custom'
| 'replace'
| 'inline'
| 'modal'
export type thememode ='light' | 'dark'
// overlay配置信息
export type OverlayOptions = {
root?: HTMLElement
mode?: String
// delayed?: boolean
// placementadobe通过轻量前段工具包floating-ui中的FloatingUIPlacement进行动态定位浮动元素
// 出现位置
placement?: String
offset?: number
// receivesFocus?: 'auto'
// notImmediatelyClosable?: boolean
// abortPromise?: Promise<boolean>
// 为没有元素触发的覆盖提供一个虚拟触发
// virtualTrigger?: VirtualTrigger
}
// export interface OverlayOpenDetail {
// content: HTMLElement;
// contentTip?: HTMLElement;
// delayed: boolean;
// offset: number;
// skidding?: number;
// placement?: Placement;
// receivesFocus?: 'auto';
// virtualTrigger?: VirtualTrigger;
// trigger: HTMLElement;
// root?: HTMLElement;
// interaction: TriggerInteractions;
// theme: ThemeData;
// notImmediatelyClosable?: boolean;
// abortPromise?: Promise<boolean>;
// }
export interface OverlayOpenDetail {
content: HTMLElement // 显示内容
offset: number // 偏移
skidding?: number // 滑动
placement?: String //显示位置
// virtualTrigger?: VirtualTrigger // 虚拟触发
trigger: HTMLElement // 触发
root?: HTMLElement // 根节点
interaction: TriggerInteractions // 交互
}

View File

@ -0,0 +1,83 @@
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

@ -16,9 +16,9 @@ import './components/picker/picker'
import './components/overflowmenu/overflowmenu'
import './components/slider/slider'
import './components/prompt/prompt'
import './components/prompt/prompt'
import './components/digicipher/digicipher'
import './components/pattern-view/pattern-view'
import './components/overlay/active-overlay'
@customElement('settings-app')
export class SettingsApp extends LitElement {

View File

@ -0,0 +1,78 @@
import {html, LitElement, css} from 'lit'
import {customElement, property} from 'lit/decorators.js'
import '../../../components/button/button'
import '../../../components/overlay/active-overlay'
import {OverlayStack} from '../../../components/overlay/overlay-stack'
@customElement('panel-activeoverlay')
export class PanelActiveOverlay extends LitElement {
constructor() {
super()
}
public overlayStack = new OverlayStack()
public test(e: Event) {
if (this.overlayStack.isOpen == false) {
// 获取被点击节点
const targetNode = e.target as HTMLElement
// 获取被点击节点父节点
const originalNode = targetNode?.parentElement as HTMLElement
// 获取要显示在overlay中的内容
const content = targetNode?.nextElementSibling as HTMLElement
if(!content) return
// 调用overlayStack中的openOverlay方法创建节点
this.overlayStack.openOverlay(originalNode, targetNode, content)
} else {
this.overlayStack.closeOverlay()
}
}
render() {
return html`
<div>
<button id="open" @click="${this.test}">overlay层</button>
<button></button>
</div>
<div style="position: fixed; top: 50%; left: 50%;">
<button id="open" @click="${this.test}">overlay层</button>
<button></button>
</div>
<div style="position: fixed; top: 98%;">
<button id="open" @click="${this.test}">overlay层</button>
<button></button>
</div>
<div style="position: fixed; top: 50%; left: 90%;">
<button id="open" @click="${this.test}">overlay层</button>
<button></button>
</div>
<div style="position: fixed; top: 98%; left: 90%;">
<button id="open" @click="${this.test}">overlay层</button>
<button></button>
</div>
`
}
static styles = css`
:host {
display: block;
width: 100vw;
height: 100vh;
}
div {
display: block;
width: 100vw;
height: 100vh;
}
`
}
declare global {
interface HTMLElementTagNameMap {
'panel-activeoverlay': PanelActiveOverlay
}
}

View File

@ -25,7 +25,8 @@ import './pattern-view/pattern-view'
import './container/homescreen-container'
import './toast/toast'
import './picker/picker'
import './prompt/prompt'
import './switch/switch'
import './activeoverlay/activeoverlay'
type SEID = string
@customElement('panel-root')
@ -186,6 +187,14 @@ export class PanelRoot extends LitElement {
href="#overflowmenu"
></star-li>
<hr />
<star-li
type=${LiType.ICON_LABEL}
label="overlay"
icon="menu"
iconcolor="yellow"
href="#activeoverlay"
></star-li>
<hr />
<star-li
type=${LiType.ICON_LABEL}
label="关于"