TASK:#109167 增加手势框架和星光组件基类代码,修复代码使其通过编译。
This commit is contained in:
parent
181443e04b
commit
71516545b8
|
@ -0,0 +1,37 @@
|
||||||
|
import {LitElement, ReactiveElement} from 'lit'
|
||||||
|
import GestureDector, {GestureEvents} from '../../lib/gesture/gesture-detector'
|
||||||
|
|
||||||
|
export interface StarInterface {
|
||||||
|
hello(): void
|
||||||
|
}
|
||||||
|
|
||||||
|
type Constructor<T = Record<string, unknown>> = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
new (...args: any[]): T
|
||||||
|
prototype: T
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StarMixin<T extends Constructor<ReactiveElement>>(
|
||||||
|
constructor: T
|
||||||
|
): T & Constructor<StarInterface> {
|
||||||
|
return class SlotTextObservingElement extends constructor {
|
||||||
|
hello() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StarElementEventMap = HTMLElementEventMap & GestureEvents
|
||||||
|
|
||||||
|
export class StarBaseElement extends StarMixin(LitElement) {
|
||||||
|
/**
|
||||||
|
* 启动手势监听框架
|
||||||
|
*/
|
||||||
|
startGestureDetector() {
|
||||||
|
GestureDector.embedded(this)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 销毁手势监听框架
|
||||||
|
*/
|
||||||
|
stopGestureDetector() {
|
||||||
|
GestureDector.disembedded(this)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,3 +16,15 @@
|
||||||
|-Dock容器
|
|-Dock容器
|
||||||
|-图标容器
|
|-图标容器
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 每层的任务
|
||||||
|
|
||||||
|
- root-container
|
||||||
|
1. 负责主屏,次屏等页容器
|
||||||
|
2. 负责dock栏容器
|
||||||
|
3. 负责主屏页面指示器
|
||||||
|
4. 负责传导编辑状态
|
||||||
|
|
||||||
|
- child-container
|
||||||
|
1. 根据不同场景类型,渲染图标,文件夹,小组件等容器
|
||||||
|
2. 负责传导编辑功能
|
|
@ -1,57 +1,144 @@
|
||||||
import {html, css, LitElement, TemplateResult, nothing} from 'lit'
|
import {html, css, LitElement, TemplateResult, nothing} from 'lit'
|
||||||
import {customElement, property, state} from 'lit/decorators.js'
|
import {customElement, state} from 'lit/decorators.js'
|
||||||
|
import {map} from 'lit/directives/map.js'
|
||||||
|
import {styleMap} from 'lit/directives/style-map.js'
|
||||||
|
import {StyleInfo} from 'lit/directives/style-map.js'
|
||||||
|
import {range} from 'lit/directives/range.js'
|
||||||
import './icon-container'
|
import './icon-container'
|
||||||
import './folder-container'
|
import './folder-container'
|
||||||
import './small-component-container'
|
import './small-component-container'
|
||||||
|
import {
|
||||||
|
ChildContainerNode,
|
||||||
|
ChildData,
|
||||||
|
FolderContainerNode,
|
||||||
|
SmallComponentContainerNode,
|
||||||
|
} from './data-type'
|
||||||
|
|
||||||
@customElement('child-container')
|
@customElement('child-container')
|
||||||
export class ChildContainer extends LitElement {
|
export class ChildContainer extends LitElement {
|
||||||
@property({type: String}) size = '6,4'
|
@state() node!: ChildContainerNode<ChildData>
|
||||||
|
@state() row = 6
|
||||||
|
@state() col = 4
|
||||||
@state() dynmaicCssVar: TemplateResult | typeof nothing = nothing
|
@state() dynmaicCssVar: TemplateResult | typeof nothing = nothing
|
||||||
|
|
||||||
attributeChangedCallback(
|
protected willUpdate() {
|
||||||
name: string,
|
if (this.node?.size) {
|
||||||
_old: string | null,
|
const result = this.node.size?.split(',')
|
||||||
value: string | null
|
this.row = Number(result?.[0])
|
||||||
) {
|
this.col = Number(result?.[1])
|
||||||
super.attributeChangedCallback(name, _old, value)
|
|
||||||
if (name === 'size') {
|
|
||||||
const result = value?.split(',')
|
|
||||||
const row = result?.[0]
|
|
||||||
const col = result?.[1]
|
|
||||||
|
|
||||||
this.dynmaicCssVar = html`
|
this.dynmaicCssVar = html`
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
--base-container-width: calc(
|
--base-grid-template-rows: ${'1fr '.repeat(this.row).trim()};
|
||||||
100% / ${row} - 2 * var(--icon-container-margin)
|
--base-grid-template-columns: ${'1fr '.repeat(this.col).trim()};
|
||||||
);
|
|
||||||
--base-container-height: calc(
|
|
||||||
100% / ${col} - 2 * var(--icon-container-margin)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsedata(data: ChildData): TemplateResult | typeof nothing {
|
||||||
|
switch (data.node) {
|
||||||
|
case 'icon-container':
|
||||||
|
return html`
|
||||||
|
<icon-container></icon-container>
|
||||||
|
`
|
||||||
|
case 'folder-container':
|
||||||
|
return html`
|
||||||
|
<folder-container
|
||||||
|
.node=${data as FolderContainerNode}
|
||||||
|
></folder-container>
|
||||||
|
`
|
||||||
|
case 'small-component-container':
|
||||||
|
return html`
|
||||||
|
<small-component-container
|
||||||
|
.node=${data as SmallComponentContainerNode}
|
||||||
|
></small-component-container>
|
||||||
|
`
|
||||||
|
default:
|
||||||
|
console.error('unhandled type: ', data.node)
|
||||||
|
return nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1 2 3 4 5
|
||||||
|
* 2 ......
|
||||||
|
* 3 ..
|
||||||
|
* 4 . .
|
||||||
|
* 5 . .
|
||||||
|
* 6 . .
|
||||||
|
* 7 . .
|
||||||
|
*
|
||||||
|
* 主屏放置三类内容:
|
||||||
|
* - 图标(1x1)
|
||||||
|
* - 文件夹(1x1)
|
||||||
|
* - 组件(1x1,1x2,2x1,2x2,2x3,3x2)
|
||||||
|
*
|
||||||
|
* 设组件尺寸为(w,h),其实位置为(x,y),其中x∈[1,6],y∈[1,4],w∈[1,3],h∈[1,3]
|
||||||
|
* 对应于gridArea,表示为(x,y,x+w,y+h)
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const childs = this.node?.childs
|
||||||
|
const divs = map(range(this.row * this.col), (_, index) => {
|
||||||
|
const x = Math.floor(index / this.col) + 1
|
||||||
|
const y = (index % this.col) + 1
|
||||||
|
const w = 1
|
||||||
|
const h = 1
|
||||||
|
const gridRowStart = x
|
||||||
|
const gridColumnStart = y
|
||||||
|
const gridRowEnd = x + w
|
||||||
|
const gridColumnEnd = y + h
|
||||||
|
|
||||||
|
const styles: StyleInfo = {
|
||||||
|
gridArea: `${gridRowStart}/${gridColumnStart}/${gridRowEnd}/${gridColumnEnd}`,
|
||||||
|
}
|
||||||
|
const child = childs?.[index]
|
||||||
|
let childNode: TemplateResult | (typeof nothing) = nothing
|
||||||
|
|
||||||
|
if (child !== undefined) {
|
||||||
|
childNode = this.parsedata(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div style=${styleMap(styles)}>${childNode}</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${this.dynmaicCssVar}
|
${this.dynmaicCssVar} ${divs}
|
||||||
<slot></slot>
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: inline-flex;
|
--base-component-width: span 1;
|
||||||
|
--base-component-height: span 1;
|
||||||
|
|
||||||
background-color: purple;
|
background-color: purple;
|
||||||
width: auto;
|
width: calc(100% - 10px);
|
||||||
height: 100%;
|
height: calc(100% - 10px);
|
||||||
margin: 10px;
|
margin: 5px;
|
||||||
flex-wrap: wrap;
|
|
||||||
align-content: start;
|
align-content: start;
|
||||||
|
/* 改为Grid布局 */
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: var(--base-grid-template-columns);
|
||||||
|
grid-template-rows: var(--base-grid-template-rows);
|
||||||
|
}
|
||||||
|
icon-container,
|
||||||
|
folder-container,
|
||||||
|
small-component-container {
|
||||||
|
margin: var(--icon-container-margin);
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
display: grid;
|
||||||
|
background: gray;
|
||||||
|
border: 1px #fff solid;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
export interface ChildContainerNode<T> {
|
||||||
|
node: string,
|
||||||
|
type: string
|
||||||
|
size?: string
|
||||||
|
childs: Array<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IconContainerNode {
|
||||||
|
node: string
|
||||||
|
childs?: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SmallComponentContainerNode {
|
||||||
|
node: string
|
||||||
|
size: string
|
||||||
|
childs?: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FolderContainerNode {
|
||||||
|
node: string
|
||||||
|
name?: string
|
||||||
|
size?: string
|
||||||
|
childs: Array<ChildContainerNode<IconContainerNode>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChildData = IconContainerNode|FolderContainerNode|SmallComponentContainerNode
|
|
@ -16,16 +16,14 @@ export class DockContainer extends LitElement {
|
||||||
<icon-container></icon-container>
|
<icon-container></icon-container>
|
||||||
<icon-container></icon-container>
|
<icon-container></icon-container>
|
||||||
<icon-container></icon-container>
|
<icon-container></icon-container>
|
||||||
<icon-container></icon-container>
|
|
||||||
<icon-container></icon-container>
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
--base-container-height: calc(100% - 6px);
|
display: grid;
|
||||||
|
grid-template-columns: var(--base-grid-template-columns);
|
||||||
display: inline-flex;
|
grid-template-rows: 1fr;
|
||||||
background-color: purple;
|
background-color: purple;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
|
|
@ -1,45 +1,68 @@
|
||||||
import {html, css, LitElement, nothing} from 'lit'
|
import {
|
||||||
import {customElement, property, state} from 'lit/decorators.js'
|
html,
|
||||||
|
css,
|
||||||
|
LitElement,
|
||||||
|
nothing,
|
||||||
|
TemplateResult,
|
||||||
|
PropertyValueMap,
|
||||||
|
} from 'lit'
|
||||||
|
import {customElement, state} from 'lit/decorators.js'
|
||||||
import {classMap} from 'lit/directives/class-map.js'
|
import {classMap} from 'lit/directives/class-map.js'
|
||||||
import {map} from 'lit/directives/map.js'
|
|
||||||
import {range} from 'lit/directives/range.js'
|
|
||||||
import './child-container'
|
import './child-container'
|
||||||
|
import { FolderContainerNode } from './data-type'
|
||||||
import './icon-container'
|
import './icon-container'
|
||||||
|
|
||||||
@customElement('folder-container')
|
@customElement('folder-container')
|
||||||
export class FolderContainer extends LitElement {
|
export class FolderContainer extends LitElement {
|
||||||
@property({type: Number}) total = 8
|
@state() node!: FolderContainerNode
|
||||||
@property({type: String}) name = '未命名'
|
|
||||||
|
|
||||||
|
/* index 从1开始计算 */
|
||||||
|
@state() index = 1
|
||||||
|
@state() pages = -1
|
||||||
@state() isopening = false
|
@state() isopening = false
|
||||||
|
|
||||||
|
@state() name = '未命名'
|
||||||
|
|
||||||
|
renderHeader(): TemplateResult | typeof nothing {
|
||||||
|
return this.isopening
|
||||||
|
? html`
|
||||||
|
<header>${this.name}</header>
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
renderIndicator(): TemplateResult | typeof nothing {
|
||||||
|
return this.isopening && this.pages > 1
|
||||||
|
? html`
|
||||||
|
<indicator-page-point
|
||||||
|
total=${this.pages}
|
||||||
|
index=${this.index}
|
||||||
|
></indicator-page-point>
|
||||||
|
`
|
||||||
|
: nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
protected willUpdate(_changedProperties: PropertyValueMap<any>) {
|
||||||
|
this.pages = this.node.childs.length
|
||||||
|
if(this.node?.name) {
|
||||||
|
this.name = this.node.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const classes = {
|
const classes = {
|
||||||
|
hidden: !this.isopening,
|
||||||
popup: this.isopening,
|
popup: this.isopening,
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
<section class=${classMap(classes)} @click=${this}>
|
<section class=${classMap(classes)} @click=${this}>
|
||||||
${this.isopening
|
${this.renderHeader()}
|
||||||
? html`
|
<child-container .node=${this.node.childs[0]}></child-container>
|
||||||
<header>${this.name}</header>
|
${this.renderIndicator()}
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
<child-container size="4,4">
|
|
||||||
${map(range(this.total), (_, index) => {
|
|
||||||
return html`
|
|
||||||
<icon-container ?hidden=${index >= 9}></icon-container>
|
|
||||||
`
|
|
||||||
})}
|
|
||||||
</child-container>
|
|
||||||
${this.isopening && this.total > 8
|
|
||||||
? html`
|
|
||||||
<indicator-page-point total="2" index="1"></indicator-page-point>
|
|
||||||
`
|
|
||||||
: nothing}
|
|
||||||
</section>
|
</section>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
@ -68,17 +91,13 @@ export class FolderContainer extends LitElement {
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
--folder-margin: 300px;
|
--folder-margin-width: 30vw;
|
||||||
|
--folder-margin-height: 20vh;
|
||||||
display: block;
|
|
||||||
width: var(--base-container-width);
|
|
||||||
height: var(--base-container-height);
|
|
||||||
background-color: lightblue;
|
background-color: lightblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
child-container {
|
child-container {
|
||||||
width: calc(100% - 20px);
|
width: calc(100% - 10px);
|
||||||
height: calc(100% - 20px);
|
height: calc(100% - 10px);
|
||||||
}
|
}
|
||||||
icon-container {
|
icon-container {
|
||||||
margin: var(--icon-container-margin);
|
margin: var(--icon-container-margin);
|
||||||
|
@ -90,15 +109,21 @@ export class FolderContainer extends LitElement {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
section.hidden {
|
||||||
|
animation-name: scale-fade-out;
|
||||||
|
animation-duration: 260ms;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
section.popup {
|
section.popup {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: var(--folder-margin);
|
padding: calc(var(--folder-margin-height)) calc(var(--folder-margin-width)/2);
|
||||||
width: calc(100vw - calc(2 * var(--folder-margin)));
|
width: calc(100vw - var(--folder-margin-width));
|
||||||
height: calc(100vh - calc(2 * var(--folder-margin)));
|
height: calc(100vh - calc(2 * var(--folder-margin-height)));
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
backdrop-filter: blur(8px); /* firefox 103 support it */
|
backdrop-filter: blur(8px); /* firefox 103 support it */
|
||||||
animation-name: scale-fade-in;
|
animation-name: scale-fade-in;
|
||||||
|
@ -112,7 +137,16 @@ export class FolderContainer extends LitElement {
|
||||||
scale: 0.5;
|
scale: 0.5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@keyframes scale-fade-out {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
scale: 1.2;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
scale: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,21 @@
|
||||||
import { html, css, LitElement } from 'lit'
|
import {html, css, LitElement} from 'lit'
|
||||||
import { customElement, property, state } from 'lit/decorators.js'
|
import {customElement} from 'lit/decorators.js'
|
||||||
|
|
||||||
@customElement('icon-container')
|
@customElement('icon-container')
|
||||||
export class IconContainer extends LitElement {
|
export class IconContainer extends LitElement {
|
||||||
@property()
|
|
||||||
foo = ''
|
|
||||||
|
|
||||||
@state()
|
|
||||||
bar = ''
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
/**
|
||||||
<!-- <span>应用角标</span>
|
* 应用左角标|应用右角标
|
||||||
<span>应用图标</span>
|
* 应用图标(应用图标加载动画)
|
||||||
<span>应用图标加载动画</span>
|
* 应用名
|
||||||
<span>应用名</span> -->
|
*/
|
||||||
`
|
return html``
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
|
||||||
width: var(--base-container-width);
|
|
||||||
height: var(--base-container-height);
|
|
||||||
background-color: lightgreen;
|
background-color: lightgreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([hidden='']) {
|
:host([hidden='']) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -36,4 +26,4 @@ declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'icon-container': IconContainer
|
'icon-container': IconContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +1,23 @@
|
||||||
import {html, css, LitElement} from 'lit'
|
import {html, css, LitElement} from 'lit'
|
||||||
import {customElement, property, state} from 'lit/decorators.js'
|
import {customElement, state} from 'lit/decorators.js'
|
||||||
|
import {map} from 'lit/directives/map.js'
|
||||||
import './child-container'
|
import './child-container'
|
||||||
import './dock-container'
|
import './dock-container'
|
||||||
import '../indicator/indicator-page-point'
|
import '../indicator/indicator-page-point'
|
||||||
|
import {ChildContainerNode, ChildData} from './data-type'
|
||||||
|
|
||||||
@customElement('root-container')
|
@customElement('root-container')
|
||||||
export class RootContainer extends LitElement {
|
export class RootContainer extends LitElement {
|
||||||
@property()
|
@state() data!: ChildContainerNode<ChildData>[]
|
||||||
foo = ''
|
|
||||||
|
|
||||||
@state()
|
|
||||||
bar = ''
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<section>
|
<section>
|
||||||
<child-container>
|
${map(this.data, (node) => {
|
||||||
<icon-container></icon-container>
|
return html`
|
||||||
<icon-container></icon-container>
|
<child-container .node=${node}></child-container>
|
||||||
<icon-container></icon-container>
|
`
|
||||||
<icon-container></icon-container>
|
})}
|
||||||
<icon-container></icon-container>
|
|
||||||
<icon-container></icon-container>
|
|
||||||
<icon-container></icon-container>
|
|
||||||
<folder-container total=4 name='文件夹1'></folder-container>
|
|
||||||
<folder-container total=7 name='文件夹2'></folder-container>
|
|
||||||
<small-component-container></small-component-container>
|
|
||||||
<icon-container></icon-container>
|
|
||||||
<folder-container total=9 name='文件夹3'></folder-container>
|
|
||||||
<folder-container total=12 name='文件夹4'></folder-container>
|
|
||||||
</child-container>
|
|
||||||
</section>
|
</section>
|
||||||
<indicator-page-point total="3" index="1"></indicator-page-point>
|
<indicator-page-point total="3" index="1"></indicator-page-point>
|
||||||
<dock-container></dock-container>
|
<dock-container></dock-container>
|
||||||
|
@ -39,7 +27,8 @@ export class RootContainer extends LitElement {
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
--icon-container-margin: 3px;
|
--icon-container-margin: 3px;
|
||||||
--base-container-width: calc(100% / 6 - 2 * var(--icon-container-margin));
|
--base-grid-template-columns: 1fr 1fr 1fr 1fr;
|
||||||
|
--base-grid-template-rows: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
--base-container-height: calc(
|
--base-container-height: calc(
|
||||||
100% / 6 - 2 * var(--icon-container-margin)
|
100% / 6 - 2 * var(--icon-container-margin)
|
||||||
);
|
);
|
||||||
|
@ -52,10 +41,10 @@ export class RootContainer extends LitElement {
|
||||||
section {
|
section {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 80%;
|
width: calc(100% - 10px);
|
||||||
height: 80%;
|
height: 80%;
|
||||||
background-color: green;
|
background-color: green;
|
||||||
margin: 10px auto 5px auto;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
indicator-page-point {
|
indicator-page-point {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
@ -65,15 +54,9 @@ export class RootContainer extends LitElement {
|
||||||
dock-container {
|
dock-container {
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: calc(15% - 30px);
|
height: calc(15% - 30px);
|
||||||
margin: 5px auto 10px auto;
|
margin: 5px auto;
|
||||||
background-color: yellow;
|
background-color: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
icon-container,
|
|
||||||
folder-container,
|
|
||||||
small-component-container {
|
|
||||||
margin: var(--icon-container-margin);
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,44 @@
|
||||||
import { html, css, LitElement } from 'lit'
|
import {html, css, LitElement, TemplateResult, nothing} from 'lit'
|
||||||
import { customElement, property, state } from 'lit/decorators.js'
|
import {customElement, property, state} from 'lit/decorators.js'
|
||||||
|
import {SmallComponentContainerNode} from './data-type'
|
||||||
|
|
||||||
@customElement('small-component-container')
|
@customElement('small-component-container')
|
||||||
export class SmallComponentContainer extends LitElement {
|
export class SmallComponentContainer extends LitElement {
|
||||||
@property()
|
@property({type: Object}) node!: SmallComponentContainerNode
|
||||||
foo = ''
|
|
||||||
|
|
||||||
@state()
|
@state() dynmaicCssVar: TemplateResult | typeof nothing = nothing
|
||||||
bar = ''
|
|
||||||
|
protected willUpdate(): void {
|
||||||
|
console.log(this.node)
|
||||||
|
if (this.node?.size) {
|
||||||
|
const size = this.node.size
|
||||||
|
const result = size?.split(',')
|
||||||
|
const row = Number(result?.[0])
|
||||||
|
const col = Number(result?.[1])
|
||||||
|
this.dynmaicCssVar = html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
--base-component-width: span ${row};
|
||||||
|
--base-component-height: span ${col};
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`
|
||||||
|
console.log(this.dynmaicCssVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
这是组件
|
${this.dynmaicCssVar}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
width: calc((var(--base-container-width) + var(--icon-container-margin)) * 2);
|
|
||||||
height: calc(var(--base-container-height) * 2);
|
|
||||||
background-color: lightcoral;
|
background-color: lightcoral;
|
||||||
|
grid-column: var(--base-component-width);
|
||||||
|
grid-row: var(--base-component-height);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
@ -29,4 +47,4 @@ declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'small-component-container': SmallComponentContainer
|
'small-component-container': SmallComponentContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,28 @@
|
||||||
- Multitasking Indicator (多任务指示器)(TODO)
|
- Multitasking Indicator (多任务指示器)(TODO)
|
||||||
- App Divider (App分割条)(TODO)
|
- App Divider (App分割条)(TODO)
|
||||||
- Resizable Indicator (可调指示器)
|
- Resizable Indicator (可调指示器)
|
||||||
- Home Indicator (主屏指示器)
|
- Home Indicator (主屏指示器)
|
||||||
|
|
||||||
|
## 主屏指示器(Home Indicator)
|
||||||
|
|
||||||
|
主屏指示器应具备的功能:
|
||||||
|
- 锁屏处,向上拨动底部可抓取的主屏指示器随手指拖动向上推动锁屏页,进入主屏页
|
||||||
|
- 应用内,向上拨动且不减速,进入主屏页
|
||||||
|
- 应用内,向上拨动且减速至0,进入任务管理页
|
||||||
|
- 主屏内,向右滑动,打开最近的后台应用
|
||||||
|
- 应用内,向右滑动,打开最近的后台应用
|
||||||
|
- 应用内,向左滑动,打开之前切换的后台应用
|
||||||
|
|
||||||
|
设计时应具备的功能:
|
||||||
|
- 手指在抓取器上的状态
|
||||||
|
- 向各个方向移动的事件指向
|
||||||
|
- 移动的二维尺度(x,y)
|
||||||
|
|
||||||
|
### 主屏指示器与父容器的通讯
|
||||||
|
|
||||||
|
自身发送事件
|
||||||
|
|
||||||
|
向上拨动y轴超出一定距离,则发出 'go-home'
|
||||||
|
向上拨动y轴超出一定距离,则发出 'go-task'
|
||||||
|
向左拨动x轴超出一定距离,则发出 'go-prev'
|
||||||
|
向右拨动x轴超出一定距离,则发出 'go-next'
|
|
@ -0,0 +1,870 @@
|
||||||
|
/**
|
||||||
|
* 手势检测库(支持单指和双指) - GestureDetector.js
|
||||||
|
* -- 针对单指或双指手势生成事件
|
||||||
|
*
|
||||||
|
* GestureDetector 对象监听指定元素上的触摸事件,并生成描述元素上的一个和两个手指手势的更高层级的事件。
|
||||||
|
*
|
||||||
|
* 支持的事件:
|
||||||
|
* - 轻触(tap) 类似单击(click)事件
|
||||||
|
* - 双指轻触(dbltap) 类似双击(dbclick)事件
|
||||||
|
* - 平移(pan) 单指平移
|
||||||
|
* - 轻扫(swipe) 在平移事件后释放手指时进行滑动
|
||||||
|
* - 长按(holdstart) 保持触摸.必须要设置一个选项才能获取这些.
|
||||||
|
* - 长按后移动(holdmove) holdstart事件后的holdmove运动
|
||||||
|
* - 长按结束(holdend) 长按/长按后移动松开手指时
|
||||||
|
* - 转换(transform) 双指捏合手势, 用于放缩和旋转
|
||||||
|
*
|
||||||
|
* 需支持的事件(同步ios):
|
||||||
|
* - Tap------------轻触--------
|
||||||
|
* - Pinch----------捏合--------
|
||||||
|
* - Rotation-------旋转--------
|
||||||
|
* - Swipe----------轻扫--------
|
||||||
|
* - Pan------------平移--------
|
||||||
|
* - ScreenEdgePan--边缘平移-----
|
||||||
|
* - LongPress------长按--------
|
||||||
|
* - Hover----------悬停--------
|
||||||
|
*
|
||||||
|
* 这些事件中的每一个都是一个冒泡的CustomEvent,事件中有重要的细节。
|
||||||
|
* 详细信息字段。事件详情尚不稳定,尚未记录。有关详细信息,请参阅对emitEvent()的调用。
|
||||||
|
*
|
||||||
|
* 使用方法:
|
||||||
|
* 1. 创建一个GestureDetector对象;
|
||||||
|
* 2. 将元素传递给 GestureDetector() 构造函数,然后对其调用 startDetecting();
|
||||||
|
* 3. 该元素将是所有发射的手势事件的目标。还可以将可选对象作为第二个构造函数参数传递。
|
||||||
|
*
|
||||||
|
* 如果您对 holdstart/holdmove/holdend 事件感兴趣,请将 {holdEvents:true} 作为第二个参数传递。
|
||||||
|
* 否则它们将不会生成。
|
||||||
|
* 如果要自定义平移阈值,请在选项参数中传递 {panThreshold:X}(X和Y以像素为单位)。
|
||||||
|
*
|
||||||
|
* 实现说明:
|
||||||
|
* 事件处理使用简单的有限状态机完成。
|
||||||
|
* 这意味着,一般来说各种手势是相互排斥的。
|
||||||
|
* 例如,在手指移动超过最小阈值之前,您不会获得平移事件,
|
||||||
|
* 但如果移动超过最小值,FSM将进入一个新状态,在该状态下,
|
||||||
|
* 它可以发出平移和滑动事件,但不能发出保持事件。
|
||||||
|
* 类似地,如果您启动了单手指平移/滑动手势,并意外地用第二根手指触摸,
|
||||||
|
* 则将继续获取平移事件,而不会突然开始获取双手指变换事件。
|
||||||
|
*
|
||||||
|
* 该库从未对其处理的任何事件调用 preventDefault() 或 stopPropagation,
|
||||||
|
* 因此原始触摸事件仍应可供其他代码处理。我不清楚这是一个特性还是一个bug。
|
||||||
|
*/
|
||||||
|
interface detectOptions {
|
||||||
|
panThreshold: number
|
||||||
|
holdEvents?: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TapPoint {
|
||||||
|
readonly screenX: number
|
||||||
|
readonly screenY: number
|
||||||
|
readonly clientX: number
|
||||||
|
readonly clientY: number
|
||||||
|
readonly timeStamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FSMGestureState {
|
||||||
|
readonly name: string
|
||||||
|
readonly init: (d: GestureDetector, e: TouchEvent, t: Touch) => void
|
||||||
|
readonly touchstart: (d: GestureDetector, e: TouchEvent, t: Touch) => void
|
||||||
|
readonly touchmove: (d: GestureDetector, e: TouchEvent, t: Touch) => void
|
||||||
|
readonly touchend: (d: GestureDetector, e: TouchEvent, t: Touch) => void
|
||||||
|
readonly holdtimeout?: (d: GestureDetector) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type TouchEventType = 'touchstart' | 'touchmove' | 'touchend'
|
||||||
|
|
||||||
|
type PickUP<T, K extends keyof T> = T[K]
|
||||||
|
|
||||||
|
type TouchHandler = PickUP<FSMGestureState, TouchEventType> | any
|
||||||
|
|
||||||
|
type TimerType = 'holdtimeout'
|
||||||
|
|
||||||
|
interface PanEvent {
|
||||||
|
readonly absolute: {
|
||||||
|
readonly dx: number
|
||||||
|
readonly dy: number
|
||||||
|
}
|
||||||
|
readonly relative: {
|
||||||
|
readonly dx: number
|
||||||
|
readonly dy: number
|
||||||
|
}
|
||||||
|
readonly position: TapPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HoldendEvent {
|
||||||
|
readonly start: TapPoint
|
||||||
|
readonly end: TapPoint
|
||||||
|
readonly dx: number
|
||||||
|
readonly dy: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SwipeEvent {
|
||||||
|
readonly start: TapPoint
|
||||||
|
readonly end: TapPoint
|
||||||
|
readonly dx: number
|
||||||
|
readonly dy: number
|
||||||
|
readonly dt: number
|
||||||
|
readonly vx: number
|
||||||
|
readonly vy: number
|
||||||
|
readonly direction: string
|
||||||
|
readonly angle: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TransformEvent {
|
||||||
|
readonly absolute: {
|
||||||
|
readonly scale: number
|
||||||
|
readonly rotate: number
|
||||||
|
}
|
||||||
|
readonly relative: {
|
||||||
|
readonly scale: number
|
||||||
|
readonly rotate: number
|
||||||
|
}
|
||||||
|
readonly midpoint: TapPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
type TapEvent = TapPoint
|
||||||
|
type DbltapEvent = TapPoint
|
||||||
|
type HoldstartEvent = PanEvent
|
||||||
|
type HoldmoveEvent = PanEvent
|
||||||
|
type TransformEndEvent = TransformEvent
|
||||||
|
|
||||||
|
// type PascalCase<T> = T extends string
|
||||||
|
// ? T extends `${infer A}${infer B}`
|
||||||
|
// ? `${Uppercase<A>}${B}Event`
|
||||||
|
// : T
|
||||||
|
// : T;
|
||||||
|
|
||||||
|
interface EmitEvents {
|
||||||
|
tap: TapEvent
|
||||||
|
dbltap: DbltapEvent
|
||||||
|
transform: TransformEvent
|
||||||
|
transformend: TransformEndEvent
|
||||||
|
pan: PanEvent
|
||||||
|
swipe: SwipeEvent
|
||||||
|
holdstart: HoldstartEvent
|
||||||
|
holdmove: HoldmoveEvent
|
||||||
|
holdend: HoldendEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmitEventType = keyof EmitEvents
|
||||||
|
type EmitEventDetail<T extends keyof EmitEvents> = EmitEvents[T]
|
||||||
|
|
||||||
|
export default class GestureDetector {
|
||||||
|
/**
|
||||||
|
* 可调参数
|
||||||
|
*/
|
||||||
|
// hold 长按事件阈值:
|
||||||
|
static HOLD_INTERVAL = 1000
|
||||||
|
// pan 平移事件抖动阈值:
|
||||||
|
static PAN_THRESHOLD = 20
|
||||||
|
static DOUBLE_TAP_DISTANCE = 50
|
||||||
|
static DOUBLE_TAP_TIME = 500
|
||||||
|
static VELOCITY_SMOOTHING = 0.5
|
||||||
|
|
||||||
|
// transform 转换事件抖动阈值:
|
||||||
|
// 分为 scale 使用的像素单位和 rotate
|
||||||
|
static SCALE_THRESHOLD = 20
|
||||||
|
static ROTATE_THRESHOLD = 22.5
|
||||||
|
|
||||||
|
// 对于平移(pans)和缩放(zooms), 我们计算初始事件和超过阈值的事件之间的新起始坐标,
|
||||||
|
// 这样我们在发送第一个事件时不会引起大的倾斜.
|
||||||
|
// 该常数区间为[0,1], 代表初始值和新选值之间的距离.
|
||||||
|
static THRESHOLD_SMOOTHING = 0.9
|
||||||
|
|
||||||
|
// 需要注册的原生事件组
|
||||||
|
eventtypes: TouchEventType[] = ['touchstart', 'touchmove', 'touchend']
|
||||||
|
|
||||||
|
private timers: any = {}
|
||||||
|
private state: FSMGestureState | any
|
||||||
|
private element: HTMLElement
|
||||||
|
|
||||||
|
emptyV = -1
|
||||||
|
emptyTouchId = -1
|
||||||
|
emptyDistance = -1
|
||||||
|
emptyDirection = -1
|
||||||
|
emptyTarget = new EventTarget()
|
||||||
|
emptyTapPoint: TapPoint = {
|
||||||
|
clientX: 0,
|
||||||
|
clientY: 0,
|
||||||
|
screenX: 0,
|
||||||
|
screenY: 0,
|
||||||
|
timeStamp: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
options: detectOptions
|
||||||
|
|
||||||
|
target!: EventTarget
|
||||||
|
|
||||||
|
start: TapPoint = this.emptyTapPoint
|
||||||
|
last: TapPoint = this.emptyTapPoint
|
||||||
|
lastTap: TapPoint = this.emptyTapPoint
|
||||||
|
lastMidpoint: TapPoint = this.emptyTapPoint
|
||||||
|
|
||||||
|
// 标识touch id
|
||||||
|
touch1!: number
|
||||||
|
touch2!: number
|
||||||
|
|
||||||
|
vx!: number
|
||||||
|
vy!: number
|
||||||
|
|
||||||
|
startDistance!: number
|
||||||
|
lastDistance!: number
|
||||||
|
startDirection!: number
|
||||||
|
lastDirection!: number
|
||||||
|
|
||||||
|
scaled!: boolean
|
||||||
|
rotated!: boolean
|
||||||
|
|
||||||
|
constructor(element: HTMLElement, options?: any) {
|
||||||
|
this.element = element
|
||||||
|
this.options = options || {}
|
||||||
|
this.options.panThreshold =
|
||||||
|
options?.panThreshold || GestureDetector.PAN_THRESHOLD
|
||||||
|
this.state = this.initialState
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FSM(Finite State Machine, 有限状态机)
|
||||||
|
*
|
||||||
|
* 以下对象是 FSM 的状态:
|
||||||
|
* initialState----------初始状态
|
||||||
|
* touchStartedState-----开始触摸状态
|
||||||
|
* panStartedState-------开始平移状态
|
||||||
|
* holdState-------------长按状态
|
||||||
|
* transformState--------变换状态
|
||||||
|
* afterTransformState---结束变换状态
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始状态 - initialState
|
||||||
|
*
|
||||||
|
* 此状态下, 不处理任何手势, 仅等待一个开始手势的事件
|
||||||
|
*/
|
||||||
|
private initialState: FSMGestureState = {
|
||||||
|
name: 'initialState',
|
||||||
|
init: (d, e, t) => {
|
||||||
|
// 当进入或返回初始状态时, 清除跟踪手势的检测器属性
|
||||||
|
// 不要在这里清除 d.lastTap, 需用其处理 dbltap 事件
|
||||||
|
d.target = this.emptyTarget
|
||||||
|
d.start = d.last = this.emptyTapPoint
|
||||||
|
d.touch1 = d.touch2 = this.emptyTouchId
|
||||||
|
d.vx = d.vy = this.emptyV
|
||||||
|
d.startDistance = d.lastDistance = this.emptyDistance
|
||||||
|
d.startDirection = d.lastDirection = this.emptyDirection
|
||||||
|
d.lastMidpoint = this.emptyTapPoint
|
||||||
|
d.scaled = d.rotated = false
|
||||||
|
|
||||||
|
// 如果调用了 touch 事件, 就开始处理它.
|
||||||
|
if (e && t && e.type === 'touchstart') {
|
||||||
|
console.info('initialState::init')
|
||||||
|
this.initialState.touchstart(d, e, t)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 切换到 touchstarted 状态并在此处理触摸事件
|
||||||
|
touchstart: (d, e, t) => {
|
||||||
|
console.info('initialState::touchstart')
|
||||||
|
d.switchTo(this.touchStartedState, e, t)
|
||||||
|
},
|
||||||
|
touchmove: () => {},
|
||||||
|
touchend: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始触摸状态 - touchStartedState
|
||||||
|
*
|
||||||
|
* 此时已有一根手指向下, 但我们还没有生成任何事件
|
||||||
|
* 等待手指的动作:
|
||||||
|
* 1. 如果手指很快抬起, 是 tap 动作;
|
||||||
|
* 2. 如果手指静止不动, 是 hold 动作;
|
||||||
|
* 3. 如果手指移动, 是 pan/swipe 动作;
|
||||||
|
* 4. 如果第二根手指向下, 是 transform 动作;
|
||||||
|
*/
|
||||||
|
private touchStartedState: FSMGestureState = {
|
||||||
|
name: 'touchStartedState',
|
||||||
|
init: (d, e, t) => {
|
||||||
|
// 记住 event target
|
||||||
|
d.target = e.target as EventTarget
|
||||||
|
// 记住开始时的 touch id
|
||||||
|
d.touch1 = t.identifier
|
||||||
|
// 获取触摸坐标
|
||||||
|
d.start = d.last = this.coordinates(e, t)
|
||||||
|
// 针对"长按"事件启动一个计时器
|
||||||
|
// 如果我们正在处理长按事件, 则为它们启动计时器.
|
||||||
|
if (d.options.holdEvents === true) {
|
||||||
|
console.info('touchStartedState::holdEvents')
|
||||||
|
d.startTimer('holdtimeout', GestureDetector.HOLD_INTERVAL)
|
||||||
|
}
|
||||||
|
console.info('touchStartedState::init')
|
||||||
|
},
|
||||||
|
|
||||||
|
touchstart: (d, e, t) => {
|
||||||
|
console.info('touchStartedState::touchstart')
|
||||||
|
console.trace()
|
||||||
|
// 如果另一只手指在本状态下落下, 就转换状态以启动双指手势.
|
||||||
|
d.clearTimer('holdtimeout')
|
||||||
|
|
||||||
|
if (e.touches.length > 1) {
|
||||||
|
// 验证我们确实有双指触摸屏幕
|
||||||
|
d.switchTo(this.transformState, e, t)
|
||||||
|
} else {
|
||||||
|
// 如果 Gecko 未能向我们发送 touchend 事件, (例如, bug 1162771)
|
||||||
|
// 然后我们可能会连续获得两个 touchstart 事件, 并在当前屏幕上只有一个手指时结束.
|
||||||
|
// 在这种情况下, 我们切换回初始状态并从该状态处理该触摸.
|
||||||
|
// (我们不直接切换到 touchStartedState, 因为我们需要重置
|
||||||
|
// initialState init() 中的内容.)
|
||||||
|
console.warn('Ignoring missing touchend event. See bug 1162771.')
|
||||||
|
d.switchTo(this.initialState, e, t)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchmove: (d, e, t) => {
|
||||||
|
// 忽略除初始触摸之外的任何触摸
|
||||||
|
// e.g.: 如果在先前的双指手势结束后仍有手指向下, 则可能发生这种情况.
|
||||||
|
if (t.identifier !== d.touch1) return
|
||||||
|
|
||||||
|
if (
|
||||||
|
Math.abs(t.screenX - d.start.screenX) > d.options.panThreshold ||
|
||||||
|
Math.abs(t.screenY - d.start.screenY) > d.options.panThreshold
|
||||||
|
) {
|
||||||
|
d.clearTimer('holdtimeout')
|
||||||
|
d.switchTo(this.panStartedState, e, t)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchend: (d, e, t) => {
|
||||||
|
// 忽略除初始触摸之外的任何触摸
|
||||||
|
if (t.identifier !== d.touch1) return
|
||||||
|
|
||||||
|
// 如果存在一个之前的tap, 它在时间和空间上足够接近, 则发出 dbltap 事件
|
||||||
|
// console.log(d.lastTap, this.emptyTapPoint)
|
||||||
|
if (
|
||||||
|
d.lastTap !== this.emptyTapPoint &&
|
||||||
|
this.isDoubleTap(d.lastTap, d.start) === true
|
||||||
|
) {
|
||||||
|
d.emitEvent('tap', d.start)
|
||||||
|
d.emitEvent('dbltap', d.start)
|
||||||
|
// 清除 lastTap 属性, 这样我们就不会得到另一个
|
||||||
|
d.lastTap = this.emptyTapPoint
|
||||||
|
} else {
|
||||||
|
// 使用起始坐标作为 event details 发出 tap 事件
|
||||||
|
d.emitEvent('tap', d.start)
|
||||||
|
|
||||||
|
// 记住这个 tap 的坐标, 这样我们就可以检测到 double taps(dbltap)
|
||||||
|
d.lastTap = this.coordinates(e, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在任何情况下, 清除计时器并转换至初始状态
|
||||||
|
d.clearTimer('holdtimeout')
|
||||||
|
d.switchTo(this.initialState)
|
||||||
|
},
|
||||||
|
|
||||||
|
holdtimeout: (d) => {
|
||||||
|
d.switchTo(this.holdState)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 变换状态 - transformState
|
||||||
|
*
|
||||||
|
* 何时进入变换状态?
|
||||||
|
* 在开始重新记录任何其他手势之前, 开始了第二次触摸.
|
||||||
|
*
|
||||||
|
* 当触摸移动时, 我们跟踪它们之间的距离和角度, 并报告变换事件(transform)中的缩放和旋转值.
|
||||||
|
*/
|
||||||
|
private transformState: FSMGestureState = {
|
||||||
|
name: 'transformState',
|
||||||
|
init: (d, e, t) => {
|
||||||
|
// 记住第2个触摸的 id
|
||||||
|
d.touch2 = t.identifier
|
||||||
|
|
||||||
|
// 获取两个 Touch 对象
|
||||||
|
const t1 = Array.prototype.some.call(
|
||||||
|
e.touches,
|
||||||
|
(i: Touch) => i.identifier === d.touch1
|
||||||
|
)
|
||||||
|
const t2 = t
|
||||||
|
|
||||||
|
// 计算并记住初始距离和角度
|
||||||
|
d.startDistance = d.lastDistance = this.touchDistance(t1, t2)
|
||||||
|
d.startDirection = d.lastDirection = this.touchDirection(t1, t2)
|
||||||
|
|
||||||
|
// 在超过阈值之前不要开始发出事件
|
||||||
|
d.scaled = d.rotated = false
|
||||||
|
},
|
||||||
|
|
||||||
|
touchstart: () => {},
|
||||||
|
|
||||||
|
touchmove: (d, e, t) => {
|
||||||
|
// 忽略未追踪的 Touches
|
||||||
|
if (t.identifier !== d.touch1 && t.identifier !== d.touch2) return
|
||||||
|
|
||||||
|
const t1: Touch = Array.prototype.some.call(
|
||||||
|
e.touches,
|
||||||
|
(i: Touch) => i.identifier === d.touch1
|
||||||
|
)
|
||||||
|
const t2: Touch = Array.prototype.some.call(
|
||||||
|
e.touches,
|
||||||
|
(i: Touch) => i.identifier === d.touch2
|
||||||
|
)
|
||||||
|
|
||||||
|
// 计算新的中点、距离和方向
|
||||||
|
const midpoint = this.midpoints(e, t1, t2)
|
||||||
|
let distance = this.touchDistance(t1, t2)
|
||||||
|
let direction = this.touchDirection(t1, t2)
|
||||||
|
const rotation = this.touchRotation(d.startDirection, direction)
|
||||||
|
|
||||||
|
// 对照阈值校验所有这些数字.
|
||||||
|
// 否则, 即使您试图保持手指不动, transform 也会过于紧张.
|
||||||
|
if (d.scaled === false) {
|
||||||
|
if (
|
||||||
|
Math.abs(distance - d.startDistance) > GestureDetector.SCALE_THRESHOLD
|
||||||
|
) {
|
||||||
|
d.scaled = true
|
||||||
|
d.startDistance = d.lastDistance = Math.floor(
|
||||||
|
d.startDistance +
|
||||||
|
GestureDetector.THRESHOLD_SMOOTHING * (distance - d.startDistance)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
distance = d.startDistance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.rotated === false) {
|
||||||
|
if (Math.abs(rotation) > GestureDetector.ROTATE_THRESHOLD) {
|
||||||
|
d.rotated = true
|
||||||
|
} else {
|
||||||
|
direction = d.startDirection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果未超过阈值, 则不必触发事件.
|
||||||
|
if (d.scaled === true || d.rotated === true) {
|
||||||
|
// The detail field for the transform gesture event includes
|
||||||
|
// 'absolute' transformations against the initial values and
|
||||||
|
// 'relative' transformations against the values from the last
|
||||||
|
// transformgesture event.
|
||||||
|
// transform 手势事件的 detail 字段包括:
|
||||||
|
// 1. 针对初始值的 'absolute' 变换
|
||||||
|
// 2. 针对上一个变换手势事件值的 'relative' 变换.
|
||||||
|
d.emitEvent('transform', {
|
||||||
|
absolute: {
|
||||||
|
// transform details since gesture start
|
||||||
|
scale: distance / d.startDistance,
|
||||||
|
rotate: this.touchRotation(d.startDirection, direction),
|
||||||
|
},
|
||||||
|
relative: {
|
||||||
|
// transform since last gesture change
|
||||||
|
scale: distance / d.lastDistance,
|
||||||
|
rotate: this.touchRotation(d.lastDirection, direction),
|
||||||
|
},
|
||||||
|
midpoint: midpoint,
|
||||||
|
})
|
||||||
|
|
||||||
|
d.lastDistance = distance
|
||||||
|
d.lastDirection = direction
|
||||||
|
d.lastMidpoint = midpoint
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
touchend: (d, _, t) => {
|
||||||
|
// 如果任何一个手指向上, 我们的手势就结束了.
|
||||||
|
// 用户可能会移动手指并再次将其放回原位, 以开始另一个双指手势,
|
||||||
|
// 因此当其中一个手指保持竖起时, 我们无法返回到初始状态.
|
||||||
|
// 另一方面, 我们无法返回到 touchStartedState,
|
||||||
|
// 因为这意味着手指向下可能会导致点击或平移事件.
|
||||||
|
// 因此, 我们需要一个 afterTransform 状态, 以等待一个手指下降或另一个手指上升.
|
||||||
|
if (t.identifier !== d.touch1 && t.identifier !== d.touch2) return
|
||||||
|
|
||||||
|
if (t.identifier === d.touch2) {
|
||||||
|
d.touch2 = this.emptyTouchId
|
||||||
|
} else if (t.identifier === d.touch1) {
|
||||||
|
d.touch1 = d.touch2
|
||||||
|
d.touch2 = this.emptyTouchId
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果我们发出了任何 transform 事件, 那现在我们需要发出一个 transformend 事件来结束这一系列.
|
||||||
|
// 此事件的 details 使用了上次触摸移动的值, 相对数量将为1和0,
|
||||||
|
// 仅仅为了完整性而包括它们, 即使它们没有用处.
|
||||||
|
if (d.scaled || d.rotated) {
|
||||||
|
d.emitEvent('transformend', {
|
||||||
|
absolute: {
|
||||||
|
// 手势开始时的 transform details
|
||||||
|
scale: d.lastDistance / d.startDistance,
|
||||||
|
rotate: this.touchRotation(d.startDirection, d.lastDirection),
|
||||||
|
},
|
||||||
|
relative: {
|
||||||
|
// 与上次 touchmove 相比, 没有任何变化
|
||||||
|
scale: 1,
|
||||||
|
rotate: 0,
|
||||||
|
},
|
||||||
|
midpoint: d.lastMidpoint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
d.switchTo(this.afterTransformState)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始平移状态 - panStartedState
|
||||||
|
*
|
||||||
|
* 此时, 单个 touch 已经移动到足以超过 pan 的阈值,
|
||||||
|
* 现在我们将在每次移动后生成 pan 事件, 并在触摸结束时生成 swipe 事件.
|
||||||
|
*
|
||||||
|
* 注意:在进行 pan/swipe 手势处理时, 忽略同时发生的任何其他触摸.
|
||||||
|
*/
|
||||||
|
private panStartedState: FSMGestureState = {
|
||||||
|
name: 'panStartedState',
|
||||||
|
init: (d, e, t) => {
|
||||||
|
// 直到 touch 移动超过某个阈值, 才开始平移.
|
||||||
|
// 但我们不希望第一个事件距离太远时, pan 突然启动.
|
||||||
|
// 因此继续, 因为平移实际上开始于沿着第一次触摸和当前触摸之间的路径的点.
|
||||||
|
d.start = d.last = this.between(d.start, this.coordinates(e, t))
|
||||||
|
|
||||||
|
// 如果我们使用 touchmove 事件转换到这种状态, 则使用 handler 处理它.
|
||||||
|
// 如果我们不这样做, 那么我们最终会出现不知道其速度的滑动事件.
|
||||||
|
if (e.type === 'touchmove') {
|
||||||
|
this.panStartedState.touchmove(d, e, t)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchstart: () => {},
|
||||||
|
touchmove: (d, e, t) => {
|
||||||
|
// 忽略未追踪的 fingers
|
||||||
|
if (t.identifier !== d.touch1) return
|
||||||
|
|
||||||
|
// 每次 touch 移动时, 发出 pan 事件, 但仍保持此状态
|
||||||
|
const current = this.coordinates(e, t)
|
||||||
|
d.emitEvent('pan', {
|
||||||
|
absolute: {
|
||||||
|
dx: current.screenX - d.start.screenX,
|
||||||
|
dy: current.screenY - d.start.screenY,
|
||||||
|
},
|
||||||
|
relative: {
|
||||||
|
dx: current.screenX - d.last.screenX,
|
||||||
|
dy: current.screenY - d.last.screenY,
|
||||||
|
},
|
||||||
|
position: current,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 跟踪 pan 速度, 以便我们可以通过 swipe 报告
|
||||||
|
// 使用一个指数移动平均值对速度进行一点平滑
|
||||||
|
const dt = current.timeStamp - d.last.timeStamp
|
||||||
|
const vx = (current.screenX - d.last.screenX) / dt
|
||||||
|
const vy = (current.screenY - d.last.screenY) / dt
|
||||||
|
|
||||||
|
if (d.vx == this.emptyV) {
|
||||||
|
// 第一次, 没有平均值
|
||||||
|
d.vx = vx
|
||||||
|
d.vy = vy
|
||||||
|
} else {
|
||||||
|
d.vx =
|
||||||
|
d.vx * GestureDetector.VELOCITY_SMOOTHING +
|
||||||
|
vx * (1 - GestureDetector.VELOCITY_SMOOTHING)
|
||||||
|
d.vy =
|
||||||
|
d.vy * GestureDetector.VELOCITY_SMOOTHING +
|
||||||
|
vy * (1 - GestureDetector.VELOCITY_SMOOTHING)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.last = current
|
||||||
|
},
|
||||||
|
touchend: (d, e, t) => {
|
||||||
|
// 忽略未追踪的 fingers
|
||||||
|
if (t.identifier !== d.touch1) return
|
||||||
|
|
||||||
|
// 当手指抬起时发出 swipe 事件.
|
||||||
|
// 报告起点和终点、dx、dy、dt、速度和方向
|
||||||
|
const current = this.coordinates(e, t)
|
||||||
|
const dx = current.screenX - d.start.screenX
|
||||||
|
const dy = current.screenY - d.start.screenY
|
||||||
|
|
||||||
|
// angle(角度)是一个正的度数, 从正x轴上的0开始, 顺时针递增.
|
||||||
|
let angle = (Math.atan2(dy, dx) * 180) / Math.PI
|
||||||
|
if (angle < 0) angle += 360
|
||||||
|
|
||||||
|
// 方向枚举: 'right', 'down', 'left' or 'up'
|
||||||
|
let direction
|
||||||
|
if (angle >= 315 || angle < 45) {
|
||||||
|
direction = 'right'
|
||||||
|
} else if (angle >= 45 && angle < 135) {
|
||||||
|
direction = 'down'
|
||||||
|
} else if (angle >= 135 && angle < 225) {
|
||||||
|
direction = 'left'
|
||||||
|
} else if (angle >= 225 && angle < 315) {
|
||||||
|
direction = 'up'
|
||||||
|
}
|
||||||
|
|
||||||
|
d.emitEvent('swipe', {
|
||||||
|
start: d.start,
|
||||||
|
end: current,
|
||||||
|
dx: dx,
|
||||||
|
dy: dy,
|
||||||
|
dt: e.timeStamp - d.start.timeStamp,
|
||||||
|
vx: d.vx,
|
||||||
|
vy: d.vy,
|
||||||
|
direction: direction,
|
||||||
|
angle: angle,
|
||||||
|
})
|
||||||
|
d.switchTo(this.initialState)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 长按状态 - holdState
|
||||||
|
*
|
||||||
|
* 何时进入长按状态?
|
||||||
|
* 用户 touch & hold 足够长的时间, 且只有较少的移动.
|
||||||
|
*
|
||||||
|
* 进入状态时的动作: 发出一个 holdstart 事件.
|
||||||
|
*
|
||||||
|
* holdstart 之后的运动生成 holdmove 事件。
|
||||||
|
* 并且当触摸结束时, 我们生成保持结束事件。
|
||||||
|
* holdmove 和 holdend 事件的使用类似于使用鼠标的UI中的拖放事件。
|
||||||
|
* 目前, 这些事件仅报告了触摸的坐标。是否还需其他细节?
|
||||||
|
*/
|
||||||
|
private holdState: FSMGestureState = {
|
||||||
|
name: 'holdState',
|
||||||
|
init: function (d) {
|
||||||
|
d.emitEvent('holdstart', d.start)
|
||||||
|
},
|
||||||
|
touchstart: () => {},
|
||||||
|
touchmove: (d, e, t) => {
|
||||||
|
var current = this.coordinates(e, t)
|
||||||
|
d.emitEvent('holdmove', {
|
||||||
|
absolute: {
|
||||||
|
dx: current.screenX - d.start.screenX,
|
||||||
|
dy: current.screenY - d.start.screenY,
|
||||||
|
},
|
||||||
|
relative: {
|
||||||
|
dx: current.screenX - d.last.screenX,
|
||||||
|
dy: current.screenY - d.last.screenY,
|
||||||
|
},
|
||||||
|
position: current,
|
||||||
|
})
|
||||||
|
|
||||||
|
d.last = current
|
||||||
|
},
|
||||||
|
|
||||||
|
touchend: (d, e, t) => {
|
||||||
|
const current = this.coordinates(e, t)
|
||||||
|
d.emitEvent('holdend', {
|
||||||
|
start: d.start,
|
||||||
|
end: current,
|
||||||
|
dx: current.screenX - d.start.screenX,
|
||||||
|
dy: current.screenY - d.start.screenY,
|
||||||
|
})
|
||||||
|
d.switchTo(this.initialState)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束变换状态 - afterTransformState
|
||||||
|
*
|
||||||
|
* 我们做一个 transform, 一个手指抬起.
|
||||||
|
* 等待那根手指落下, 或者另一根手指也向上.
|
||||||
|
*/
|
||||||
|
private afterTransformState: FSMGestureState = {
|
||||||
|
name: 'afterTransformState',
|
||||||
|
init: () => {},
|
||||||
|
touchstart: (d, e, t) => {
|
||||||
|
d.switchTo(this.transformState, e, t)
|
||||||
|
},
|
||||||
|
touchend: (d, _, t) => {
|
||||||
|
if (t.identifier === d.touch1) {
|
||||||
|
d.switchTo(this.initialState)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchmove: () => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触摸点坐标生成器
|
||||||
|
*
|
||||||
|
* 返回一个包含触摸事件空间和时间坐标的对象.
|
||||||
|
* 我们冻结对象以使其不可变, 用于在事件中传递它, 而不用担心值被更改.
|
||||||
|
*/
|
||||||
|
private coordinates(e: TouchEvent, t: Touch): TapPoint {
|
||||||
|
return Object.freeze({
|
||||||
|
screenX: t.screenX,
|
||||||
|
screenY: t.screenY,
|
||||||
|
clientX: t.clientX,
|
||||||
|
clientY: t.clientY,
|
||||||
|
timeStamp: this.eventTime(e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查两个 tap 在时间和空间上是否足够接近, 以触发 dbltap 事件.
|
||||||
|
*/
|
||||||
|
private isDoubleTap(lastTap: TapPoint, thisTap: TapPoint): boolean {
|
||||||
|
// console.log(lastTap, thisTap)
|
||||||
|
const dx = Math.abs(thisTap.screenX - lastTap.screenX)
|
||||||
|
const dy = Math.abs(thisTap.screenY - lastTap.screenY)
|
||||||
|
const dt = thisTap.timeStamp - lastTap.timeStamp
|
||||||
|
|
||||||
|
return (
|
||||||
|
dx < GestureDetector.DOUBLE_TAP_DISTANCE &&
|
||||||
|
dy < GestureDetector.DOUBLE_TAP_DISTANCE &&
|
||||||
|
dt < GestureDetector.DOUBLE_TAP_TIME
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两次 touch 之间的距离
|
||||||
|
*/
|
||||||
|
private touchDistance(t1: Touch, t2: Touch): number {
|
||||||
|
const dx = t2.screenX - t1.screenX
|
||||||
|
const dy = t2.screenY - t1.screenY
|
||||||
|
return Math.sqrt(dx * dx + dy * dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算两次 touch 间直线的方向(单位为角度)
|
||||||
|
*
|
||||||
|
* 返回一个数字 d, 区间为 (-180, 180]
|
||||||
|
*/
|
||||||
|
private touchDirection(t1: Touch, t2: Touch): number {
|
||||||
|
return (
|
||||||
|
(Math.atan2(t2.screenY - t1.screenY, t2.screenX - t1.screenX) * 180) /
|
||||||
|
Math.PI
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取中间点函数
|
||||||
|
*
|
||||||
|
* 类似于 coordinates(), 但返回的是两次触摸之间的中点
|
||||||
|
*/
|
||||||
|
private midpoints(e: TouchEvent, t1: Touch, t2: Touch): TapPoint {
|
||||||
|
return Object.freeze({
|
||||||
|
screenX: Math.floor((t1.screenX + t2.screenX) / 2),
|
||||||
|
screenY: Math.floor((t1.screenY + t2.screenY) / 2),
|
||||||
|
clientX: Math.floor((t1.clientX + t2.clientX) / 2),
|
||||||
|
clientY: Math.floor((t1.clientY + t2.clientY) / 2),
|
||||||
|
timeStamp: this.eventTime(e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回事件的时间戳(毫秒)
|
||||||
|
*
|
||||||
|
* 在Gecko中, 合成事件似乎以微秒而不是毫秒为单位.
|
||||||
|
* 因此, 如果时间戳比当前时间大得多, 假设它以微秒为单位, 除以1000.
|
||||||
|
*/
|
||||||
|
private eventTime(e: TouchEvent): number {
|
||||||
|
const ts = e.timeStamp
|
||||||
|
if (ts > 2 * Date.now()) return Math.floor(ts / 1000)
|
||||||
|
else return ts
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角度计算函数
|
||||||
|
*
|
||||||
|
* 计算方向d1和方向d2之间的顺时针角度.
|
||||||
|
* 返回角度 angle 所属区间 (-180, 180]
|
||||||
|
*/
|
||||||
|
private touchRotation(d1: number, d2: number): number {
|
||||||
|
let angle = d2 - d1
|
||||||
|
if (angle > 180) angle -= 360
|
||||||
|
else if (angle <= -180) angle += 360
|
||||||
|
return angle
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成中间点函数
|
||||||
|
*
|
||||||
|
* 给定坐标对象 c1 和 c2, 返回表示沿着这些点之间的线的点和时间的新坐标对象。
|
||||||
|
* 点的位置由阈值平滑常数(THRESHOLD_SMOOTHING)控制
|
||||||
|
*/
|
||||||
|
private between(c1: TapPoint, c2: TapPoint): TapPoint {
|
||||||
|
const r = GestureDetector.THRESHOLD_SMOOTHING
|
||||||
|
return Object.freeze({
|
||||||
|
screenX: Math.floor(c1.screenX + r * (c2.screenX - c1.screenX)),
|
||||||
|
screenY: Math.floor(c1.screenY + r * (c2.screenY - c1.screenY)),
|
||||||
|
clientX: Math.floor(c1.clientX + r * (c2.clientX - c1.clientX)),
|
||||||
|
clientY: Math.floor(c1.clientY + r * (c2.clientY - c1.clientY)),
|
||||||
|
timeStamp: Math.floor(c1.timeStamp + r * (c2.timeStamp - c1.timeStamp)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public startDetecting(): void {
|
||||||
|
this.eventtypes.forEach((t) => {
|
||||||
|
this.element.addEventListener(t, this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopDetecting(): void {
|
||||||
|
this.eventtypes.forEach((t) => {
|
||||||
|
this.element.removeEventListener(t, this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleEvent(e: TouchEvent): void {
|
||||||
|
let handler: TouchHandler = this.state[e.type]
|
||||||
|
if (!handler) return
|
||||||
|
|
||||||
|
// 如果这是 touch 事件, 请分别处理每个更改的 touch
|
||||||
|
if (e.changedTouches) {
|
||||||
|
for (let i = 0; i < e.changedTouches.length; i++) {
|
||||||
|
console.log(this.state.name)
|
||||||
|
handler(this, e, e.changedTouches[i])
|
||||||
|
// 第一次改变的 touch 可能改变了 FSM 的状态.
|
||||||
|
handler = this.state[e.type]
|
||||||
|
if (!handler) return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 否则, 直接将事件派分给 handler
|
||||||
|
handler(this, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private startTimer(type: TimerType, time: number): void {
|
||||||
|
this.clearTimer(type)
|
||||||
|
this.timers[type] = setTimeout(() => {
|
||||||
|
this.timers[type] = null
|
||||||
|
// 以下即为 this.state.holdtimeout(this)
|
||||||
|
this.state[type]?.(this)
|
||||||
|
}, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearTimer(type: TimerType): void {
|
||||||
|
if (this.timers[type]) {
|
||||||
|
clearTimeout(this.timers[type])
|
||||||
|
this.timers[type] = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FSM状态切换函数
|
||||||
|
*
|
||||||
|
* 切换到一个新的 FSM State, 并调用该状态对应的 init() 方法(如果存在的话).
|
||||||
|
* event 和 touch 都是可选的, 仅传递给状态初始化函数。
|
||||||
|
*/
|
||||||
|
private switchTo(
|
||||||
|
state: FSMGestureState,
|
||||||
|
event?: TouchEvent,
|
||||||
|
touch?: Touch
|
||||||
|
): void {
|
||||||
|
this.state = state
|
||||||
|
state?.init(this, event as TouchEvent, touch as Touch)
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitEvent(
|
||||||
|
type: EmitEventType,
|
||||||
|
detail: EmitEventDetail<EmitEventType>
|
||||||
|
): void {
|
||||||
|
if (this.target === this.emptyTarget) {
|
||||||
|
console.error('Attempt to emit event with no target')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// const event = this.element.ownerDocument.createEvent('CustomEvent');
|
||||||
|
// event.initCustomEvent(type, true, true, detail);
|
||||||
|
// this.target.dispatchEvent(event);
|
||||||
|
|
||||||
|
const event = new CustomEvent(type, {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
detail: detail,
|
||||||
|
})
|
||||||
|
console.log(event)
|
||||||
|
this.target.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
import {html, css} from 'lit'
|
||||||
|
import {customElement} from 'lit/decorators.js'
|
||||||
|
import GestureDector from '../../lib/gesture/gesture-detector'
|
||||||
|
import {StarBaseElement} from '../base/star-base-element'
|
||||||
|
|
||||||
|
@customElement('home-bar-indicator')
|
||||||
|
export class HomeBarIndicator extends StarBaseElement {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
this.startGestureDetector()
|
||||||
|
this.updateComplete.then(() => {
|
||||||
|
console.log(this)
|
||||||
|
const log = (e: any) => {
|
||||||
|
console.log(e.type, e.target)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chrome 上调用 e.preventDedault() 可阻止右键菜单弹出,同时不会影响
|
||||||
|
* 正常 touch 事件的分发。
|
||||||
|
*
|
||||||
|
* Firefox 上调用 e.preventDedault() 可阻止右键菜单弹出,但会中断 touch 事件,
|
||||||
|
* 表现为:长按(touchstart),中断,手抬起再按(touchmove)
|
||||||
|
*
|
||||||
|
* 测试长按事件时,请使用终端设备或Chrome
|
||||||
|
*/
|
||||||
|
this.addEventListener(
|
||||||
|
'contextmenu',
|
||||||
|
(e) => {
|
||||||
|
e.stopImmediatePropagation()
|
||||||
|
if (navigator.vendor === 'Google Inc.') {
|
||||||
|
e.preventDefault()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 同时监听 tap 和 doubletap 事件时,注意设置可选参数的次序
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
// this.addEventListener('tap', log, {once:true});
|
||||||
|
// this.addEventListener('doubletap', log);
|
||||||
|
// this.addEventListener('panstart', log);
|
||||||
|
// this.addEventListener('panmove', log);
|
||||||
|
// this.addEventListener('panend', log);
|
||||||
|
// this.addEventListener('swipe', log);
|
||||||
|
// this.addEventListener('holdstart', log);
|
||||||
|
|
||||||
|
let myElement = document.createElement('button')
|
||||||
|
myElement.setAttribute('style', 'height:200px;width:200px;')
|
||||||
|
myElement = GestureDector.embedded(myElement)
|
||||||
|
myElement.addEventListener('tap', log)
|
||||||
|
myElement.addEventListener('doubletap', log, {once: true})
|
||||||
|
myElement.addEventListener('holdstart', log)
|
||||||
|
// console.log(GestureDector)
|
||||||
|
// GestureDector.disembedded(myElement)
|
||||||
|
this.shadowRoot?.appendChild(myElement)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html``
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: yellow;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: yellow;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'home-bar-indicator': HomeBarIndicator
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import {html, css, LitElement} from 'lit'
|
import {html, LitElement} from 'lit'
|
||||||
import {customElement, property, state} from 'lit/decorators.js'
|
import {customElement, property, state} from 'lit/decorators.js'
|
||||||
import GaiaContainerChild from './gaia-container-child'
|
import GaiaContainerChild from './gaia-container-child'
|
||||||
import GestureManager from './gesture-manager'
|
import GestureManager from './gesture-manager'
|
||||||
|
@ -376,7 +376,7 @@ class GaiaContainer extends LitElement {
|
||||||
this._dnd.top +
|
this._dnd.top +
|
||||||
'px)'
|
'px)'
|
||||||
// 控制与文件夹交互时的拖拽操作
|
// 控制与文件夹交互时的拖拽操作
|
||||||
const child = this._dnd.child as unknown as GaiaContainerChild
|
// const child = this._dnd.child as unknown as GaiaContainerChild
|
||||||
// if (child) {
|
// if (child) {
|
||||||
// child.container.style.setProperty(
|
// child.container.style.setProperty(
|
||||||
// '--offset-position-left',
|
// '--offset-position-left',
|
||||||
|
@ -564,7 +564,7 @@ class GaiaContainer extends LitElement {
|
||||||
return gridId
|
return gridId
|
||||||
}
|
}
|
||||||
|
|
||||||
getFolderGridIdByCoordinate(x: number, y: number, pagination: number = 0) {
|
getFolderGridIdByCoordinate(x: number, y: number, _: number = 0) {
|
||||||
if (!this.openedFolder || !this.openedFolder._status) return -1
|
if (!this.openedFolder || !this.openedFolder._status) return -1
|
||||||
let page = this.pages[this.openedFolder.pagination]
|
let page = this.pages[this.openedFolder.pagination]
|
||||||
|
|
||||||
|
@ -1269,7 +1269,7 @@ class GaiaContainer extends LitElement {
|
||||||
* 交换被拖拽元素位置
|
* 交换被拖拽元素位置
|
||||||
*/
|
*/
|
||||||
dropElement_bak() {
|
dropElement_bak() {
|
||||||
let {dropTarget, dropChild, isPage, pagination, gridId} =
|
let {dropTarget, dropChild, isPage, pagination} =
|
||||||
this.getChildFromPoint(
|
this.getChildFromPoint(
|
||||||
this._dnd.gridPosition.x +
|
this._dnd.gridPosition.x +
|
||||||
(this._dnd.last.pageX - this._dnd.start.pageX),
|
(this._dnd.last.pageX - this._dnd.start.pageX),
|
||||||
|
@ -1443,7 +1443,7 @@ class GaiaContainer extends LitElement {
|
||||||
pushAhead: boolean = true
|
pushAhead: boolean = true
|
||||||
// 此次交换是否向上挤压
|
// 此次交换是否向上挤压
|
||||||
pushUp: boolean = true
|
pushUp: boolean = true
|
||||||
dropElement(mode: 'delay' | 'immediately' = 'delay') {
|
dropElement(mode: 'delay' | 'immediately' = 'delay'): any {
|
||||||
const distanceX =
|
const distanceX =
|
||||||
this._dnd.gridPosition.x + this._dnd.last.pageX - this._dnd.start.pageX
|
this._dnd.gridPosition.x + this._dnd.last.pageX - this._dnd.start.pageX
|
||||||
const distanceY =
|
const distanceY =
|
||||||
|
@ -1451,6 +1451,7 @@ class GaiaContainer extends LitElement {
|
||||||
const {dropTarget, dropChild, isPage, pagination, gridId} =
|
const {dropTarget, dropChild, isPage, pagination, gridId} =
|
||||||
this.getChildFromPoint(distanceX, distanceY)
|
this.getChildFromPoint(distanceX, distanceY)
|
||||||
const child = this._dnd.child
|
const child = this._dnd.child
|
||||||
|
console.log(pagination)
|
||||||
if (
|
if (
|
||||||
this._staticElements.includes(dropTarget) &&
|
this._staticElements.includes(dropTarget) &&
|
||||||
(child !== dropChild || (gridId == dropChild.gridId && mode == 'delay'))
|
(child !== dropChild || (gridId == dropChild.gridId && mode == 'delay'))
|
||||||
|
@ -1460,7 +1461,7 @@ class GaiaContainer extends LitElement {
|
||||||
|
|
||||||
this._dnd.dropTarget = dropTarget
|
this._dnd.dropTarget = dropTarget
|
||||||
// 拖拽元素悬浮页面默认为当前页面
|
// 拖拽元素悬浮页面默认为当前页面
|
||||||
const suspendingPage = isPage ? dropTarget! : this.pages[this.pagination]
|
// const suspendingPage = isPage ? dropTarget! : this.pages[this.pagination]
|
||||||
if (child.isTail && isPage) {
|
if (child.isTail && isPage) {
|
||||||
if (this._dnd.lastDropChild) {
|
if (this._dnd.lastDropChild) {
|
||||||
this._dnd.lastDropChild.master.classList.remove('merging')
|
this._dnd.lastDropChild.master.classList.remove('merging')
|
||||||
|
|
|
@ -545,7 +545,7 @@ export default class ExchangeStrategy {
|
||||||
this.placedRecorder = {}
|
this.placedRecorder = {}
|
||||||
this.placedChild.add(child)
|
this.placedChild.add(child)
|
||||||
this.pickChild(child, recorder)
|
this.pickChild(child, recorder)
|
||||||
const dropChild = recorder[gridId]
|
// const dropChild = recorder[gridId]
|
||||||
// 获取被挤占位置的元素,按照优先级排序
|
// 获取被挤占位置的元素,按照优先级排序
|
||||||
const pickChildren = this.getChildrenByGridArea(
|
const pickChildren = this.getChildrenByGridArea(
|
||||||
gridId,
|
gridId,
|
||||||
|
|
|
@ -379,6 +379,14 @@ export default class GaiaContainerFolder extends GaiaContainerChild {
|
||||||
if (this._status && evt.target === this.container) {
|
if (this._status && evt.target === this.container) {
|
||||||
this.close()
|
this.close()
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
this._status &&
|
||||||
|
(evt.target as HTMLElement).tagName !== 'SITE-ICON'
|
||||||
|
) {
|
||||||
|
evt.preventDefault()
|
||||||
|
evt.stopImmediatePropagation()
|
||||||
|
}
|
||||||
|
break
|
||||||
case 'touchstart':
|
case 'touchstart':
|
||||||
case 'touchmove':
|
case 'touchmove':
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -33,14 +33,14 @@ export class StarAnimateSection extends LitElement {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%; /* 100vw会有x轴溢出 */
|
width: 100%; /* 100vw会有x轴溢出 */
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
/* height: calc(100vh + 1px); */ /* 手动制造溢出 */
|
/* height: calc(100vh + 1px); */ /* 手动制造溢出 */
|
||||||
animation-duration: 0.3s;
|
animation-duration: 0.3s;
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
/* overflow: auto; */
|
overflow: auto;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,7 +188,7 @@ export class StarSlider extends LitElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private touchEnd(evt: TouchEvent) {
|
private touchEnd(_: TouchEvent) {
|
||||||
if (this.tick) {
|
if (this.tick) {
|
||||||
return console.log(10 * Math.round(parseInt(this.endValue) / 10) + '')
|
return console.log(10 * Math.round(parseInt(this.endValue) / 10) + '')
|
||||||
} else {
|
} else {
|
||||||
|
@ -229,7 +229,7 @@ export class StarSlider extends LitElement {
|
||||||
this.vProgress.style.setProperty('height', this.newY + 'px')
|
this.vProgress.style.setProperty('height', this.newY + 'px')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private touchEndVertical(evt: TouchEvent) {
|
private touchEndVertical(_: TouchEvent) {
|
||||||
if (!this.disabled) {
|
if (!this.disabled) {
|
||||||
this.vProgress.style.setProperty('--vWidth', '8px')
|
this.vProgress.style.setProperty('--vWidth', '8px')
|
||||||
this.vSliderBar.style.setProperty('--vWidth', '8px')
|
this.vSliderBar.style.setProperty('--vWidth', '8px')
|
||||||
|
|
|
@ -60,7 +60,7 @@ export class StarSwitch extends LitElement {
|
||||||
this.startx = evt.touches[0].clientX
|
this.startx = evt.touches[0].clientX
|
||||||
this._x = this.startx
|
this._x = this.startx
|
||||||
}
|
}
|
||||||
private touchEnd(evt: TouchEvent) {
|
private touchEnd(_: TouchEvent) {
|
||||||
this.rightx = 0
|
this.rightx = 0
|
||||||
this.leftx = 10000
|
this.leftx = 10000
|
||||||
this.startx = 0
|
this.startx = 0
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,143 @@
|
||||||
|
# 手势检测框架
|
||||||
|
|
||||||
|
## Gesture Detector Finite State Machine
|
||||||
|
|
||||||
|
|
||||||
|
<center><h2>FSM状态转移表</h2></center>
|
||||||
|
|
||||||
|
当前状态→<br>条件↓ | initialState | touchStartedState | touchesStartedState | holdState | panState | swipeState | pinchState | rotateState
|
||||||
|
-|-|-|-|-|-|-|-|-
|
||||||
|
收到 touchstart | touchStartedState
|
||||||
|
收到 touchstart 且触摸点=1 | | initialState
|
||||||
|
收到 touchstart 且触摸点>1 | | touchesStartedState
|
||||||
|
收到 touchstart 且判定进行捏 | | | pinchState
|
||||||
|
收到 touchstart 且判定进行旋转 | | | rotateState
|
||||||
|
收到 touchstart 且判定进行平移 | | | panState
|
||||||
|
收到 touchmove 且移动差值>平移阈值 || panState
|
||||||
|
收到 touchend ||initialState||initialState|swipeState|initialState |initialState|initialState
|
||||||
|
hold超时完成 || holdState
|
||||||
|
|
||||||
|
|
||||||
|
## Gesture Events
|
||||||
|
|
||||||
|
### TapEvent
|
||||||
|
|
||||||
|
- definition: one or 2 fingers press, lift
|
||||||
|
- Usage: Select / Zoom in
|
||||||
|
- Event: tap
|
||||||
|
- Interface
|
||||||
|
```idl
|
||||||
|
Interface TapEvent {
|
||||||
|
readonly attribute TouchList touches;
|
||||||
|
readonly attribute long fingers; // only support 1 and 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PanEvent
|
||||||
|
|
||||||
|
- Definition: One or 2 fingers press, move, lift
|
||||||
|
- Usage: Dismiss, scroll, tilt / select multiple items, pan, tilt
|
||||||
|
- Event: panstart/panmove/panend
|
||||||
|
- Interface
|
||||||
|
```idl
|
||||||
|
Interface PanEvent {
|
||||||
|
readonly attribute TouchList startTouches;
|
||||||
|
readonly attribute TouchList endTouches;
|
||||||
|
readonly attribute TouchList movingTouches;
|
||||||
|
readonly attribute long fingers; // only support 1 and 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### SwipeEvent
|
||||||
|
- Definition: One fingers press, move quickly, lift
|
||||||
|
- Usage: Dismiss, scroll, tilt / select multiple items, pan, tilt
|
||||||
|
- Event: swipestart/swipemove/swipeend
|
||||||
|
- Interface
|
||||||
|
```idl
|
||||||
|
Interface SwipeEvent {
|
||||||
|
readonly attribute Touch startTouch;
|
||||||
|
readonly attribute Touch endTouch;
|
||||||
|
readonly attribute double velocity;
|
||||||
|
Readonly attribute double direction;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### PinchEvent
|
||||||
|
|
||||||
|
- Definition: Two fingers press, move in/outwards, lift
|
||||||
|
- Usage: Zoom in/out
|
||||||
|
- Event: pinchstart/pinchmove/pinchend
|
||||||
|
- Interface
|
||||||
|
```idl
|
||||||
|
Interface Enum PinchZoom {
|
||||||
|
IN,
|
||||||
|
OUT
|
||||||
|
}
|
||||||
|
|
||||||
|
Interface PinchEvent {
|
||||||
|
readonly attribute TouchList startTouches;
|
||||||
|
readonly attribute TouchList endTouches;
|
||||||
|
readonly attribute double scale;
|
||||||
|
readonly attribute enum PinchZoom zoom;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### LongPressEvent
|
||||||
|
|
||||||
|
- Definition: One or 2 fingers press, wait, lift
|
||||||
|
- Usage: Select an element,
|
||||||
|
- Event: longpress
|
||||||
|
- Interface
|
||||||
|
```idl
|
||||||
|
Interface LongPressEvent {
|
||||||
|
readonly attribute TouchList pressTouches;
|
||||||
|
readonly attribute long fingers; // only support 1 and 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DoubleTapEvent
|
||||||
|
|
||||||
|
- Definition: One or 2 fingers press, lift, one or 2 fingers press, lift
|
||||||
|
- Usage: Selection / Context menu
|
||||||
|
- Event: doubletap
|
||||||
|
- Interface
|
||||||
|
```idl
|
||||||
|
Interface DoubleTapEvent {
|
||||||
|
readonly attribute TouchList firstTapTouches;
|
||||||
|
readonly attribute TouchList secondTapTouches;
|
||||||
|
readonly attribute long fingers; // only support 1 and 2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### RotateEvent
|
||||||
|
|
||||||
|
- Definition: 2 fingers press, simultaneously orbit both fingers around the center point, lift.
|
||||||
|
- Usage: Rotate content, a picture or a map.
|
||||||
|
- Event: rotatestart/rotatemove/rotateend
|
||||||
|
- Interface
|
||||||
|
```ts
|
||||||
|
Interface RotateEvent {
|
||||||
|
readonly attribute TouchList startTouches;
|
||||||
|
readonly attribute TouchList endTouches;
|
||||||
|
readonly attribute TouchList rotatingTouches;
|
||||||
|
readonly attribute double degree;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
/* Using the this keyword to access the element property of the current object. */
|
||||||
|
// this.element.addEventListener = new Proxy(this.element.addEventListener, {
|
||||||
|
// set(target: any, _: any, val: any) {
|
||||||
|
// debug(target, _, val)
|
||||||
|
// // if (typeof val == 'object') {
|
||||||
|
// // target.push(val)
|
||||||
|
// // if (target.length > 10) target.shift()
|
||||||
|
// // }
|
||||||
|
// return true
|
||||||
|
// },
|
||||||
|
// get(target, prop: any, _) {
|
||||||
|
// debug(target, _, prop)
|
||||||
|
// return target[prop]
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
```
|
|
@ -1,12 +1,111 @@
|
||||||
import { html, css, LitElement } from 'lit'
|
import {html, css, LitElement} from 'lit'
|
||||||
import { customElement } from 'lit/decorators.js'
|
import {customElement} from 'lit/decorators.js'
|
||||||
import '../../../components/container/root-container'
|
import '../../../components/container/root-container'
|
||||||
|
import {
|
||||||
|
ChildContainerNode,
|
||||||
|
ChildData,
|
||||||
|
} from '../../../components/container/data-type'
|
||||||
|
|
||||||
@customElement('panel-container')
|
@customElement('panel-container')
|
||||||
export class PanelContainer extends LitElement {
|
export class PanelContainer extends LitElement {
|
||||||
|
/* 测试用数据 */
|
||||||
|
data: ChildContainerNode<ChildData>[] = [
|
||||||
|
{
|
||||||
|
node: 'child-container',
|
||||||
|
type: 'main',
|
||||||
|
childs: [
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'small-component-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'small-component-container', size: '2,2'},
|
||||||
|
{
|
||||||
|
node: 'folder-container',
|
||||||
|
name: '文件夹1',
|
||||||
|
childs: [
|
||||||
|
{
|
||||||
|
node: 'child-container',
|
||||||
|
type: 'main',
|
||||||
|
size: '3,3',
|
||||||
|
childs: [{node: 'icon-container'}, {node: 'icon-container'}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'folder-container',
|
||||||
|
name: '文件夹2',
|
||||||
|
childs: [
|
||||||
|
{
|
||||||
|
node: 'child-container',
|
||||||
|
type: 'main',
|
||||||
|
size: '4,4',
|
||||||
|
childs: [{node: 'icon-container'}, {node: 'icon-container'}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'folder-container',
|
||||||
|
name: '文件夹3',
|
||||||
|
childs: [
|
||||||
|
{
|
||||||
|
node: 'child-container',
|
||||||
|
type: 'main',
|
||||||
|
size: '4,3',
|
||||||
|
childs: [{node: 'icon-container'}, {node: 'icon-container'}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'folder-container',
|
||||||
|
name: '文件夹4',
|
||||||
|
childs: [
|
||||||
|
{
|
||||||
|
node: 'child-container',
|
||||||
|
type: 'main',
|
||||||
|
size: '6,6',
|
||||||
|
childs: [{node: 'icon-container'}, {node: 'icon-container'}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'folder-container',
|
||||||
|
name: '文件夹5',
|
||||||
|
childs: [
|
||||||
|
{
|
||||||
|
node: 'child-container',
|
||||||
|
type: 'main',
|
||||||
|
size: '3,3',
|
||||||
|
childs: [
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: 'child-container',
|
||||||
|
type: 'main',
|
||||||
|
size: '3,3',
|
||||||
|
childs: [
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
{node: 'icon-container'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<root-container></root-container>
|
<root-container .data=${this.data}></root-container>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,8 +118,8 @@ export class PanelContainer extends LitElement {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
root-container {
|
root-container {
|
||||||
width: 90vw;
|
width: calc(100% - 10px);
|
||||||
height: 90vh;
|
height: calc(100% - 10px);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -30,4 +129,4 @@ declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'panel-container': PanelContainer
|
'panel-container': PanelContainer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {html, css, LitElement, TemplateResult} from 'lit'
|
import {html, css, LitElement} from 'lit'
|
||||||
import {customElement, query, state} from 'lit/decorators.js'
|
import {customElement, query, state} from 'lit/decorators.js'
|
||||||
import GaiaContainer from '../../../components/grid-container/container'
|
import GaiaContainer from '../../../components/grid-container/container'
|
||||||
import GaiaContainerChild from '../../../components/grid-container/gaia-container-child'
|
import GaiaContainerChild from '../../../components/grid-container/gaia-container-child'
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {html, css, LitElement} from 'lit'
|
import {html, LitElement} from 'lit'
|
||||||
import {customElement, property, query} from 'lit/decorators.js'
|
import {customElement, property, query} from 'lit/decorators.js'
|
||||||
import style from './icon-style'
|
import style from './icon-style'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import {html, css, LitElement} from 'lit'
|
||||||
|
import {customElement, query, state} from 'lit/decorators.js'
|
||||||
|
import '../../../components/grabber/home-bar-indicator'
|
||||||
|
import {HomeBarIndicator} from '../../../components/grabber/home-bar-indicator'
|
||||||
|
|
||||||
|
@customElement('panel-home-indicator')
|
||||||
|
export class PanelHomeIndicator extends LitElement {
|
||||||
|
@query('home-indicator', true) homeIndicator!: HomeBarIndicator
|
||||||
|
// @property({type: String}) status = this.homeIndicator?.status
|
||||||
|
foo = ''
|
||||||
|
|
||||||
|
@state()
|
||||||
|
bar = ''
|
||||||
|
|
||||||
|
|
||||||
|
attributeChangedCallback(name: string, _old: string, value: string): void {
|
||||||
|
super.attributeChangedCallback(name, _old, value)
|
||||||
|
console.log(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
// setInterval(() => {
|
||||||
|
// console.log(this.homeIndicator.movePoint)
|
||||||
|
// }, 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<home-bar-indicator></home-bar-indicator>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css``
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'panel-home-indicator': PanelHomeIndicator
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
import {html, css, LitElement, CSSResultArray} from 'lit'
|
import {html, css, LitElement, CSSResultArray} from 'lit'
|
||||||
import {customElement, queryAll, state} from 'lit/decorators.js'
|
import {customElement, state} from 'lit/decorators.js'
|
||||||
import {sharedStyles} from '../shared-styles'
|
import {sharedStyles} from '../shared-styles'
|
||||||
import '../../../components/indicator/indicator-page-point'
|
import '../../../components/indicator/indicator-page-point'
|
||||||
import {IndicatorPagePoint} from '../../../components/indicator/indicator-page-point'
|
|
||||||
|
|
||||||
@customElement('panel-indicators')
|
@customElement('panel-indicators')
|
||||||
export class PanelIndicators extends LitElement {
|
export class PanelIndicators extends LitElement {
|
||||||
|
@ -16,8 +15,6 @@ export class PanelIndicators extends LitElement {
|
||||||
this.index < 1 ? 1 : this.index > this.total ? this.total : this.index
|
this.index < 1 ? 1 : this.index > this.total ? this.total : this.index
|
||||||
}
|
}
|
||||||
|
|
||||||
@queryAll('.myindicator') private myindicators!: IndicatorPagePoint
|
|
||||||
|
|
||||||
handleEvent(evt: Event) {
|
handleEvent(evt: Event) {
|
||||||
switch (evt.type) {
|
switch (evt.type) {
|
||||||
case 'click':
|
case 'click':
|
||||||
|
|
|
@ -16,7 +16,7 @@ export class PanelOverflowMenu extends LitElement {
|
||||||
@property({type: Number}) state = 0
|
@property({type: Number}) state = 0
|
||||||
|
|
||||||
// 关闭菜单
|
// 关闭菜单
|
||||||
closeoverflowmenu(e) {
|
closeoverflowmenu(e: any) {
|
||||||
// 获取点击事件所在的标签名字
|
// 获取点击事件所在的标签名字
|
||||||
const tagName = e.target.tagName.toLowerCase()
|
const tagName = e.target.tagName.toLowerCase()
|
||||||
// 判断是否点击的star-overflowmenu标签
|
// 判断是否点击的star-overflowmenu标签
|
||||||
|
|
|
@ -11,6 +11,7 @@ import './icon/icon'
|
||||||
import './general/general'
|
import './general/general'
|
||||||
import './card/card'
|
import './card/card'
|
||||||
import './indicators/indicators'
|
import './indicators/indicators'
|
||||||
|
import './indicators/home-indicator'
|
||||||
import './blur/use-blur'
|
import './blur/use-blur'
|
||||||
import './button/button'
|
import './button/button'
|
||||||
import './container/container'
|
import './container/container'
|
||||||
|
@ -175,6 +176,14 @@ export class PanelRoot extends LitElement {
|
||||||
href="#indicators"
|
href="#indicators"
|
||||||
></star-li>
|
></star-li>
|
||||||
<hr />
|
<hr />
|
||||||
|
<star-li
|
||||||
|
type=${LiType.ICON_LABEL}
|
||||||
|
label="home指示器"
|
||||||
|
icon="accessibility"
|
||||||
|
iconcolor="blue"
|
||||||
|
href="#home-indicator"
|
||||||
|
></star-li>
|
||||||
|
<hr />
|
||||||
<star-li
|
<star-li
|
||||||
type=${LiType.ICON_LABEL}
|
type=${LiType.ICON_LABEL}
|
||||||
label="毛玻璃"
|
label="毛玻璃"
|
||||||
|
|
|
@ -27,6 +27,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "temp/gaia-container-child.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"references": [{"path": "./tsconfig.node.json"}]
|
"references": [{"path": "./tsconfig.node.json"}]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue