diff --git a/src/components/overflowmenu/README.md b/src/components/overflowmenu/README.md
index 0025813..e7aef75 100644
--- a/src/components/overflowmenu/README.md
+++ b/src/components/overflowmenu/README.md
@@ -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
+ }
+ }
+ }
+```
\ No newline at end of file
diff --git a/src/components/overlay/README.md b/src/components/overlay/README.md
index 682a37f..004a984 100644
--- a/src/components/overlay/README.md
+++ b/src/components/overlay/README.md
@@ -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)
+```
\ No newline at end of file
diff --git a/src/components/overlay/active-overlay.ts b/src/components/overlay/active-overlay.ts
new file mode 100644
index 0000000..3bdcd0b
--- /dev/null
+++ b/src/components/overlay/active-overlay.ts
@@ -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`
+
+
+
+ `
+ 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
+ }
+}
diff --git a/src/components/overlay/overlay-stack.ts b/src/components/overlay/overlay-stack.ts
new file mode 100644
index 0000000..3b2e954
--- /dev/null
+++ b/src/components/overlay/overlay-stack.ts
@@ -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
+ }
+}
diff --git a/src/components/overlay/overlay-types.ts b/src/components/overlay/overlay-types.ts
new file mode 100644
index 0000000..4b95485
--- /dev/null
+++ b/src/components/overlay/overlay-types.ts
@@ -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
+ // 为没有元素触发的覆盖提供一个虚拟触发
+ // 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;
+// }
+
+export interface OverlayOpenDetail {
+ content: HTMLElement // 显示内容
+ offset: number // 偏移
+ skidding?: number // 滑动
+ placement?: String //显示位置
+ // virtualTrigger?: VirtualTrigger // 虚拟触发
+ trigger: HTMLElement // 触发
+ root?: HTMLElement // 根节点
+ interaction: TriggerInteractions // 交互
+}
diff --git a/src/components/overlay/overlay.ts b/src/components/overlay/overlay.ts
new file mode 100644
index 0000000..180f6f7
--- /dev/null
+++ b/src/components/overlay/overlay.ts
@@ -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 {
+ /* 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)
+ }
+}
diff --git a/src/index.ts b/src/index.ts
index 5c8a6c9..c250661 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -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 {
diff --git a/src/test/panels/activeoverlay/activeoverlay.ts b/src/test/panels/activeoverlay/activeoverlay.ts
new file mode 100644
index 0000000..730f28c
--- /dev/null
+++ b/src/test/panels/activeoverlay/activeoverlay.ts
@@ -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`
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+ }
+
+ static styles = css`
+ :host {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+ }
+ div {
+ display: block;
+ width: 100vw;
+ height: 100vh;
+ }
+ `
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'panel-activeoverlay': PanelActiveOverlay
+ }
+}
diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts
index 7366785..a892bc3 100644
--- a/src/test/panels/root.ts
+++ b/src/test/panels/root.ts
@@ -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"
>
+
+