Merge branch 'master' into feature-component-digicipher
This commit is contained in:
commit
95f46ab5f3
|
@ -3,28 +3,43 @@
|
|||
## 介绍
|
||||
|
||||
### overflowmenu:溢出菜单。
|
||||
现有overflowmenu包含一个绑定点击事件的star-button按钮,通过在响应函数closeoverflowmenu控制溢出菜单内容的显示与隐藏。
|
||||
|
||||
后续改进:结合active-overlay组件,通过调用overlay-stack类控制溢出菜单的显示与隐藏
|
||||
|
||||
## 新需求(主页面要求——罗 9.5)
|
||||
|
||||
1. 外部颜色控制(思路:使用自定义 css 样式,如: --test-color:XXX 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
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -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:延迟出现overlay;placement:自定义位置;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替换回原位置
|
||||
>>> 移除overlay,removeoverlay节点
|
||||
>>> 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)
|
||||
```
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
// placement:adobe通过轻量前段工具包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 // 交互
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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="关于"
|
||||
|
|
Loading…
Reference in New Issue