Merge branch 'master' of ssh://172.20.184.160:7999/yr/star-web-components into featurn-component-locked

This commit is contained in:
duanzhijiang 2022-09-26 16:49:59 +08:00
commit 1731b05a1e
41 changed files with 2031 additions and 405 deletions

View File

@ -0,0 +1,192 @@
import {html, css, LitElement, HTMLTemplateResult} from 'lit'
import {customElement, property, query} from 'lit/decorators.js'
@customElement('brightness-slider')
export class BrightnessSlider extends LitElement {
@query('.progress') progress!: HTMLDivElement
@query('.sliderBar') sliderBar!: HTMLDivElement
@property({type: String}) id = ''
@property({type: String}) _coverLen = ''
@property({type: Number}) innerWidth = 0
@property({type: Number}) distance = 0
@property({type: Number}) currentDistance = 0
@property({type: Number}) barWidth = 0
@property({type: Number}) max = 100
@property({type: Number}) value = 1
@property() touchAction = {
// 触摸开始落点
start: {
offsetX: 0,
offsetY: 0,
clientX: 0,
clientY: 0,
},
// 触摸结束落点
last: {
offsetX: 0,
offsetY: 0,
clientX: 0,
clientY: 0,
},
}
@property({type: String})
get coverLen() {
return this._coverLen
}
set coverLen(value: string) {
this.style.setProperty('--cover-length', value)
this._coverLen = value
}
render(): HTMLTemplateResult {
return html`
<div
class="sliderBar"
data-icon="brightness"
value=${this.value}
max=${this.max}
@touchstart=${this}
@touchmove=${this}
@touchend=${this}
>
<div class="progress"></div>
</div>
`
}
static styles = css`
:host {
width: inherit;
height: inherit;
dislay: block;
--cover-length: 3.78px; // 亮度为1
--background-lm: rgba(255, 255, 255, 0.5);
--background-dm: rgba(0, 0, 0, 0.15);
--progress-background-lm: rgba(255, 255, 255, 0.8);
--progress-background-dm: rgba(255, 255, 255, 0.7);
}
.sliderBar {
width: inherit;
height: inherit;
position: absolute;
left: 0px;
right: 0px;
background: var(--background-lm);
}
.progress {
width: var(--cover-length);
height: 100%;
position: absolute;
left: 0px;
right: 0px;
background: var(--progress-background-lm);
}
.sliderBar::before {
position: absolute;
content: attr(data-icon);
font-family: gaia-icons;
font-style: normal;
text-rendering: optimizelegibility;
font-weight: 500;
}
@media screen and (min-width: 900px) {
.sliderBar {
border-radius: 16px;
}
.progress {
border-radius: 16px;
}
.sliderBar::before {
width: 48px;
height: 48px;
left: 29px;
top: 29px;
font-size: 48px;
}
}
@media screen and (min-width: 600px) {
.sliderBar {
border-radius: 8px;
}
.progress {
border-radius: 8px;
}
.sliderBar::before {
width: 24px;
height: 24px;
left: 14px;
top: 14px;
font-size: 24px;
}
}
`
firstUpdated() {
if (this.value) {
let len = Math.floor((this.value * this.sliderBar.offsetWidth) / this.max)
this.coverLen = len.toString()
}
}
handleEvent(event: TouchEvent) {
switch (event.type) {
case 'touchstart':
this.touchAction.start.clientX = event.touches[0].clientX
this.barWidth = this.sliderBar.offsetWidth
break
case 'touchmove':
this.touchAction.last.clientX = event.touches[0].clientX
this.distance =
this.touchAction.last.clientX - this.touchAction.start.clientX
this.currentDistance = this.distance + this.progress.offsetWidth
if (this.currentDistance < 0) {
// this.currentDistance = 0;
this.currentDistance = this.barWidth / this.max
}
if (this.currentDistance > this.barWidth) {
this.currentDistance = this.barWidth
}
this.progress.style.setProperty('width', this.currentDistance + 'px')
break
case 'touchend':
this.value = Math.floor(
(this.currentDistance * this.max) / this.barWidth
)
this.dispatchEvent(
new CustomEvent('brightness-slider-change', {
detail: {
value: this.value,
},
bubbles: true,
composed: true,
})
)
break
}
}
setValue(value: number) {
this.value = value
let len = Math.floor((value * this.sliderBar.offsetWidth) / this.max)
this.coverLen = len.toString()
}
}
declare global {
interface HTMLElementTagNameMap {
'brightness-slider': BrightnessSlider
}
}

View File

@ -0,0 +1 @@
export * from './brightness-slider.js'

View File

@ -0,0 +1,22 @@
{
"name": "@star-web-components/brightness-slider",
"version": "0.0.1",
"description": "",
"type": "module",
"main": "./index.js",
"module": "./index.js",
"exports": {
".": {
"default": "./index.js"
},
"./index": {
"default": "./index.js"
},
"./brightness-slider.js": {
"default": "./brightness-slider.js"
},
"./package.json": "./package.json"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "../../"
},
"include": ["*.ts"]
}

View File

@ -102,4 +102,10 @@ export default css`
align-items: center; align-items: center;
margin: auto; margin: auto;
} }
/* menubutton */
:host(#menuitem) button {
font-size: 24px;
height: 76px;
}
` `

View File

@ -0,0 +1,173 @@
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
import {customElement, property, queryAssignedElements} from 'lit/decorators.js'
import {HeaderBarType} from '../header-bar/header-bar.js'
import {
IconControlBar,
IconControlBarType,
} from '../icon-control-bar/icon-control-bar.js'
import {WebActivity} from './interface.js'
import '../icon-control-bar/icon-control-bar.js'
import '../header-bar/header-bar.js'
export enum DropDownMenuType {
HEADER_WITH_TIME = 'header-with-time',
HEADER_WITH_DATE_TIME = 'header-with-date-time',
}
@customElement('drop-down-menu')
export class DropDownMenu extends LitElement {
@property({type: DropDownMenuType}) type = ''
@queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]
getOnlyTime(): HTMLTemplateResult {
return html`
<div class="inner">
<header-bar type=${HeaderBarType.ONLY_TIME}>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="compose"
@click=${this}
></icon-control-bar>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="settings"
@click=${this}
></icon-control-bar>
</header-bar>
<div class="all-quick-icons">
<slot @slotchange=${this}></slot>
</div>
</div>
`
}
getTimeAndDate(): HTMLTemplateResult {
return html`
<div class="inner">
<header-bar type=${HeaderBarType.DATE_TIME}>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="settings"
@click=${this}
></icon-control-bar>
</header-bar>
<div class="others">
<slot @slotchange=${this}></slot>
</div>
</div>
`
}
render(): HTMLTemplateResult | typeof nothing {
switch (this.type) {
case DropDownMenuType.HEADER_WITH_TIME:
return this.getOnlyTime()
case DropDownMenuType.HEADER_WITH_DATE_TIME:
return this.getTimeAndDate()
default:
console.error('unhandled 【pull-down-menu】 type')
return nothing
}
}
static styles = css`
:host {
width: inherit;
height: inherit;
}
:host([open]) {
transform: translateY(0);
transition: transform 300ms ease, visibility 300ms;
will-change: transform;
visibility: visible;
}
.inner {
width: inherit;
height: inherit;
display: flex;
flex-direction: column;
align-items: center;
}
@media screen and (min-width: 900px) {
header-bar[type='only-time'] {
dispaly: flex;
width: 512px;
height: 52px;
}
header-bar[type='date-time'] {
dispaly: flex;
width: 860px;
height: 64px;
}
.all-quick-icons {
width: 100%;
height: calc(100% - 52px);
position: relative;
top: 40px;
}
.others {
display: flex;
flex-direction: column;
position: absolute;
top: 100px;
}
}
@media screen and (min-width: 600px) {
header-bar[type='only-time'] {
dispaly: flex;
width: 256px;
height: 26px;
}
header-bar[type='date-time'] {
dispaly: flex;
width: 430px;
height: 32px;
}
.all-quick-icons {
width: 100%;
height: calc(100% - 26px);
position: relative;
top: 20px;
}
.others {
display: flex;
flex-direction: column;
position: absolute;
top: 50px;
}
}
`
handleEvent(event: Event) {
switch (event.type) {
case 'click':
switch ((event.target as IconControlBar).icon) {
case 'compose':
window.dispatchEvent(new CustomEvent('editor-action'))
break
case 'settings':
let activity = new WebActivity('configure', {})
activity.start().then(
() => {},
() => {}
)
break
}
break
}
}
}
declare global {
interface HTMLElementTagNameMap {
'drop-down-menu': DropDownMenu
}
}

View File

@ -0,0 +1 @@
export * from './drop-down-menu.js'

View File

@ -0,0 +1,9 @@
export interface WebActivity {
start(): Promise<any>
cancel(): void
}
export var WebActivity: {
prototype: WebActivity
new (name: string, data?: any): WebActivity
}

View File

@ -0,0 +1,22 @@
{
"name": "@star-web-components/drop-down-menu",
"version": "0.0.1",
"description": "",
"type": "module",
"main": "./index.js",
"module": "./index.js",
"exports": {
".": {
"default": "./index.js"
},
"./index": {
"default": "./index.js"
},
"./drop-down-menu.js": {
"default": "./drop-down-menu.js"
},
"./package.json": "./package.json"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,13 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "../../"
},
"include": [
"*.ts",
"../icon-control-bar/icon-control-bar.js",
"../header-bar/header-bar.js",
"./interface.js"
]
}

View File

@ -9,6 +9,12 @@
## 主屏指示器(Home Indicator) ## 主屏指示器(Home Indicator)
定义Home Indicator为了达成全面屏苹果移除了实体 Home 键,取而代之的是一条 134 x 5 pt 的虚拟 Home 指示条。参照 Macbook Pro 的 Touch Bar感觉库克对可以滑动的 Bar 真是有迷之热爱。
1px \* 3/4 = 1pt
苹果全面屏底部多出了高度为 34 的 Home Indicator 区域。
主屏指示器应具备的功能: 主屏指示器应具备的功能:
- 锁屏处,向上拨动底部可抓取的主屏指示器随手指拖动向上推动锁屏页,进入主屏页 - 锁屏处,向上拨动底部可抓取的主屏指示器随手指拖动向上推动锁屏页,进入主屏页

View File

@ -1,6 +1,5 @@
import {html, css} from 'lit' import {html, css, CSSResultArray} from 'lit'
import {customElement} from 'lit/decorators.js' import {customElement} from 'lit/decorators.js'
import GestureDector from '../../lib/gesture/gesture-detector'
import {StarBaseElement} from '../base/star-base-element' import {StarBaseElement} from '../base/star-base-element'
@customElement('home-bar-indicator') @customElement('home-bar-indicator')
@ -8,77 +7,30 @@ export class HomeBarIndicator extends StarBaseElement {
constructor() { constructor() {
super() super()
this.startGestureDetector() 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)
})
} }
/**
*
* @returns
*/
render() { render() {
return html`` return html``
} }
static styles = css` public static override get styles(): CSSResultArray {
:host { return [
position: absolute; css`
bottom: 0; :host {
width: 100vw; position: absolute;
height: 100vh; bottom: 0;
background-color: yellow; width: 100%;
user-select: none; height: 34px; /* 1px * 3/4 = 1pt */
} background-color: yellow;
a { user-select: none;
position: absolute; }
width: 100%; `,
height: 100%; ]
background-color: yellow; }
}
`
} }
declare global { declare global {

View File

@ -0,0 +1,327 @@
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
import {customElement, property, query, state} from 'lit/decorators.js'
export enum HeaderBarType {
ONLY_TIME = 'only-time',
DATE_TIME = 'date-time',
}
@customElement('header-bar')
export class HeaderBar extends LitElement {
@state() updateTimeTimeout!: number
@state() updateDateTimeout!: number
@property({type: HeaderBarType}) type = ''
@query('#time') time!: HTMLDivElement
@query('#date') date!: HTMLDivElement
getTime(): HTMLTemplateResult {
return html`
<div class="time-outer">
<div id="time"></div>
<div class="time-icons">
<slot></slot>
</div>
</div>
`
}
getTimeAndDate(): HTMLTemplateResult {
return html`
<div class="time-date-outer">
<div class="time-date">
<div id="time"></div>
<div id="date"></div>
</div>
<div class="time-date-icons">
<slot></slot>
</div>
</div>
`
}
render() {
switch (this.type) {
case HeaderBarType.ONLY_TIME:
return this.getTime()
case HeaderBarType.DATE_TIME:
return this.getTimeAndDate()
default:
console.error('unhandled 【header-bar】 type')
return nothing
}
}
static styles = css`
:host {
--only-time-color-lm: #292929;
--only-time-color-dm: #f0f0f0;
--time-date-time-color-lm: #404040;
--time-date-date-color-lm: #000000;
--time-date-time-color-dm: #e0e0e0;
--time-date-date-color-dm: #e0e0e0;
width: inherit;
height: inherit;
}
.time-outer {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.time-outer > #time {
font-family: OPPOSans;
font-style: normal;
font-weight: 400;
color: var(--only-time-color-lm);
}
.time-icons > ::slotted(*) {
display: flex;
}
.time-date-outer {
width: 100%;
height: 100%;
display: flex;
align-items: end;
}
.time-date {
height: 100%;
width: 100%;
display: flex;
align-items: end;
vertical-align: middle;
font-family: 'OPPOSans';
font-style: normal;
font-weight: 400;
position: relative;
}
.time-date > #time {
color: var(--time-date-time-color-lm);
}
#date {
position: relative;
opacity: 0.6;
color: var(--time-date-date-color-lm);
mix-blend-mode: normal;
}
.host([deep-mode]) .time-outer > #time {
color: var(--only-time-color-dm);
}
.host([deep-mode]) #date {
color: var(--time-date-date-color-dm);
}
@media screen and (min-width: 900px) {
.time-outer > #time {
height: 52px;
line-height: 52px;
font-size: 52px;
}
.time-icons {
display: flex;
width: 136px;
position: relative;
right: 4px;
}
.time-icons > ::slotted(:last-child) {
position: relative;
left: 40px;
}
.time-date {
line-height: 64px;
left: 10px;
}
.time-date > #time {
font-size: 64px;
}
#date {
height: 34px;
line-height: 34px;
left: 22px;
font-size: 26px;
}
.time-date-icons {
position: relative;
right: 11px;
}
}
@media screen and (min-width: 600px) {
.time-outer > #time {
height: 26px;
line-height: 26px;
font-size: 26px;
}
.time-icons {
display: flex;
width: 68px;
position: relative;
right: 2px;
}
.time-icons > ::slotted(:last-child) {
position: relative;
left: 20px;
}
.time-date {
line-height: 32px;
left: 5px;
}
.time-date > #time {
font-size: 32px;
}
#date {
height: 17px;
line-height: 17px;
left: 11px;
font-size: 13px;
}
.time-date-icons {
position: relative;
right: 5.5px;
}
}
`
firstUpdated() {
this.autoUpdateDateTime()
}
autoUpdateDateTime() {
switch (this.type) {
case HeaderBarType.ONLY_TIME:
window.clearTimeout(this.updateTimeTimeout)
this.autoUpdateTime()
break
case HeaderBarType.DATE_TIME:
window.clearTimeout(this.updateDateTimeout)
window.clearTimeout(this.updateTimeTimeout)
this.autoUpdateDate()
this.autoUpdateTime()
break
}
}
autoUpdateTime() {
var d = new Date()
this.time.innerHTML = this.formatTime(d)
let self = this
var remainMillisecond = (60 - d.getSeconds()) * 1000
this.updateTimeTimeout = window.setTimeout(() => {
self.autoUpdateTime()
}, remainMillisecond)
}
autoUpdateDate() {
var d = new Date()
this.date.innerHTML = this.formatDate(d) + ' ' + this.getWeekDay()
let remainMillisecond =
(24 - d.getHours()) * 3600 * 1000 -
d.getMinutes() * 60 * 1000 -
d.getMilliseconds()
let self = this
this.updateDateTimeout = window.setTimeout(function updateDateTimeout() {
self.autoUpdateDate()
}, remainMillisecond)
}
getWeekDay() {
var d = new Date()
let daynumber = d.getDay()
let day = ''
switch (daynumber) {
case 0:
day = '周日'
break
case 1:
day = '周一'
break
case 2:
day = '周二'
break
case 3:
day = '周三'
break
case 4:
day = '周四'
break
case 5:
day = '周五'
break
case 6:
day = '周六'
break
}
return day
}
formatDate(d: Date | string, iso?: boolean) {
if (d instanceof Date) {
if (iso) {
let date = d.toISOString() // Thu Oct 07 2021 07:45:18 GMT+0800
date = date.split('T')[0]
return date
} else {
return d.toLocaleString(navigator.languages as string[], {
// year: 'numeric',
month: 'long',
day: '2-digit',
})
}
} else {
return d
}
}
formatTime(d: Date | string, iso?: boolean) {
if (d instanceof Date) {
if (iso) {
let time = d.toLocaleTimeString()
time = time.split(' ')[0]
if (time.indexOf(':') === 1) {
time = `0${time}`
}
return time
} else {
return d.toLocaleString(navigator.languages as string[], {
// hour12: (navigator as any).mozHour12,
hour12: false,
hour: 'numeric',
minute: 'numeric',
})
}
} else {
if (d.indexOf(':') == 1) {
d = '0' + d
}
return d
}
}
}
declare global {
interface HTMLElementTagNameMap {
'header-bar': HeaderBar
}
}

View File

@ -0,0 +1 @@
export * from './header-bar.js'

View File

@ -0,0 +1,22 @@
{
"name": "@star-web-components/header-bar",
"version": "0.0.1",
"description": "",
"type": "module",
"main": "./index.js",
"module": "./index.js",
"exports": {
".": {
"default": "./index.js"
},
"./index": {
"default": "./index.js"
},
"./header-bar.js": {
"default": "./header-bar.js"
},
"./package.json": "./package.json"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "../../"
},
"include": ["*.ts"]
}

View File

@ -0,0 +1,152 @@
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
import {customElement, property, queryAssignedElements} from 'lit/decorators.js'
export enum IconControlBarGroupType {
BASE = 'base',
WITH_STATE = 'with-state',
}
@customElement('icon-control-bar-group')
export class IconControlBarGroup extends LitElement {
@property({type: IconControlBarGroupType}) type = ''
@property({type: Number}) count = 2
@queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]
getbase(): HTMLTemplateResult {
const colorstyle =
this.count !== 4
? html`
<style>
.icon-only > div > ::slotted(*) {
width: calc(100% / ${this.count}) !important;
}
</style>
`
: nothing
return html`
<div class="icon-only">
<div>
<slot></slot>
</div>
${colorstyle}
</div>
`
}
getIconWithState(): HTMLTemplateResult {
return html`
<div class="icon-with-state">
<slot></slot>
</div>
`
}
firstUpdated() {
this.count = this.slotElements.length
}
render() {
switch (this.type) {
case IconControlBarGroupType.BASE:
return this.getbase()
case IconControlBarGroupType.WITH_STATE:
return this.getIconWithState()
default:
console.error('unhandled 【icon-control-bar-group】 type')
return nothing
}
}
static styles = css`
:host {
--background-lm: #ffffff;
--background-dm: #000000;
--opacity-lm: 0.75;
--opacity-dm: 0.25;
--line-border-lm: rgba(0, 0, 0, 0.07);
--line-border-dm: rgba(255, 255, 255, 0.07);
width: inherit;
height: inherit;
background: var(--background-lm);
mix-blend-mode: normal;
opacity: var(--opacity-lm);
}
.icon-with-state {
width: 100%;
display: flex;
align-items: center;
// justify-content: space-around;
justify-content: space-between;
}
.icon-only {
width: inherit;
height: inherit;
background: inherit;
border-radius: inherit;
opacity: inherit;
}
.icon-only > div {
display: flex;
align-items: center;
height: 100%;
}
.icon-only > div > ::slotted(*) {
width: 25%;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.icon-only > div > ::slotted(:first-child) {
border-left-style: none;
}
:host([deep-mode]) {
background: var(--background-dm);
}
@media screen and (min-width: 900px) {
:host {
border-radius: 16px;
}
.icon-only > div > ::slotted(*) {
height: 30px;
border-left: 2px solid var(--line-border-lm);
border-radius: 2px;
}
:host([deep-mode]) .icon-only > div > ::slotted(*) {
border-left: 2px solid var(--line-border-dm);
}
}
@media screen and (min-width: 600px) {
:host {
border-radius: 8px;
}
.icon-only > div > ::slotted(*) {
height: 15px;
border-left: 1px solid var(--line-border-lm);
border-radius: 1px;
}
:host([deep-mode]) .icon-only > div > ::slotted(*) {
border-left: 1px solid var(--line-border-dm);
}
}
`
}
declare global {
interface HTMLElementTagNameMap {
'icon-control-bar-group': IconControlBarGroup
}
}

View File

@ -0,0 +1 @@
export * from './icon-control-bar-group.js'

View File

@ -0,0 +1,22 @@
{
"name": "@star-web-components/icon-control-bar-group",
"version": "0.0.1",
"description": "",
"type": "module",
"main": "./index.js",
"module": "./index.js",
"exports": {
".": {
"default": "./index.js"
},
"./index": {
"default": "./index.js"
},
"./icon-control-bar-group.js": {
"default": "./icon-control-bar-group.js"
},
"./package.json": "./package.json"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "../../"
},
"include": ["*.ts"]
}

View File

@ -0,0 +1,415 @@
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
import {
customElement,
property,
eventOptions,
query,
state,
} from 'lit/decorators.js'
import {WebActivity} from './interface.js'
export enum IconControlBarType {
BASE = 'base',
BASE_WITHOUT_BORDER = 'base-without-border',
WITH_STATE = 'with-state',
}
@customElement('icon-control-bar')
export class IconControlBar extends LitElement {
@property({type: IconControlBarType}) type = ''
@property({type: String}) icon = ''
@property({type: String}) stateDesc = ''
@property({type: String}) settingsKey = ''
@property({type: Boolean}) bgchange = false
@property({type: String}) id = ''
@property({type: Boolean}) active = false
@state({}) timer!: NodeJS.Timeout
@query('.more-info-icon') moreInfoIcon!: HTMLDivElement
@query('.icon-button') iconBtn!: HTMLDivElement
getbase(): HTMLTemplateResult | typeof nothing {
if (!this.icon) {
console.error('【icon-control-bar】缺少 icon 参数')
return nothing
}
const iconstyle = html`
<style>
@media screen and (min-width: 900px) {
:host {
width: 104px;
height: 104px;
}
}
@media screen and (min-width: 600px) {
:host {
width: 52px;
height: 52px;
}
}
</style>
`
return html`
<div
class="icon-button icon-base with-border"
data-icon=${this.icon}
@click=${this.handleClick}
@touchstart=${this.handlePress}
@touchend=${this.handlePressEnd}
>
${iconstyle}
</div>
`
}
getbaseWithOutBorder(): HTMLTemplateResult | typeof nothing {
if (!this.icon) {
console.error('【icon-control-bar】缺少 icon 参数')
return nothing
}
return html`
<div
class="icon-button icon-base without-border"
data-icon=${this.icon}
@click=${this.handleClick}
@touchstart=${this.handlePress}
@touchend=${this.handlePressEnd}
></div>
`
}
getwithstate(): HTMLTemplateResult | typeof nothing {
if (!this.icon) {
console.error('【icon-control-bar】缺少 icon 参数')
return nothing
}
const iconstyle = html`
<style>
@media screen and (min-width: 900px) {
:host {
width: 240px;
height: 108px;
}
}
@media screen and (min-width: 600px) {
:host {
width: 120px;
height: 54px;
}
}
</style>
`
return html`
<div
class="icon-button icon-with-state with-border config-activity"
data-icon=${this.icon}
@click=${this.handleClick}
@touchstart=${this.handlePress}
@touchend=${this.handlePressEnd}
>
<p>${this.stateDesc}</p>
<div
class="more-info-icon"
@click=${this.handleInfo}
data-icon="expand-left"
></div>
${iconstyle}
</div>
`
}
@eventOptions({passive: false})
handleClick(event: Event) {
let isActive = true // 闹铃
if (this.bgchange) {
let target = event.target as HTMLElement
if (target.nodeName == 'P') {
target = target.parentElement as HTMLElement
}
if (target.className == 'more-info-icon') {
target = target.parentElement as HTMLElement
}
isActive = target.classList.contains('active')
this.activeOrInactive(isActive, target)
}
// if (this.type == IconControlBarType.WITH_STATE) {
// if (target.className == "more-info-icon") {
// target = target.parentElement as HTMLElement;
// }
// if (target.classList.contains("active")) {
// this.moreInfoIcon.dataset.icon = "expand-left";
// } else {
// this.moreInfoIcon.dataset.icon = "";
// }
// }
let self = this
window.dispatchEvent(
new CustomEvent('icon-control-bar-click', {
detail: {
id: self.id ? self.id : self.icon,
isActive: isActive,
target: self,
},
})
)
}
handleInfo() {
let self = this
window.dispatchEvent(
new CustomEvent('more-info-action', {detail: self.icon})
)
}
handlePress(event: Event) {
let target = event.target as HTMLElement
if (target.nodeName == 'P') {
target = target.parentElement as HTMLElement
}
if (this.getAttribute('disabled') === 'true') {
return
}
let section = this.icon
this.timer = setTimeout(() => {
let activity = new WebActivity('moz_configure_window', {
data: {
target: 'device',
section: section,
},
})
activity.start()
}, 300)
let self = this
window.dispatchEvent(
new CustomEvent('touch-start', {
detail: {
id: self.id ? self.id : self.icon,
target: self,
},
})
)
}
handlePressEnd() {
clearTimeout(this.timer)
}
activeOrInactive(isActive: boolean, element?: HTMLElement) {
if (element == null) {
isActive
? this.iconBtn.classList.remove('active')
: this.iconBtn.classList.add('active')
this.active = isActive
} else {
isActive
? element.classList.remove('active')
: element.classList.add('active')
}
}
render(): HTMLTemplateResult | typeof nothing {
switch (this.type) {
case IconControlBarType.BASE:
return this.getbase()
case IconControlBarType.BASE_WITHOUT_BORDER:
return this.getbaseWithOutBorder()
case IconControlBarType.WITH_STATE:
return this.getwithstate()
default:
console.error('unhandled 【icon-control-bar】 type')
return nothing
}
}
addClass(className: string[]) {
this.iconBtn.classList.add(...className)
}
removeClass(className: string) {
this.iconBtn.classList.remove(className)
}
getClassList() {
return this.iconBtn.classList
}
static styles = css`
:host {
--background-active: #1d98f5;
--background-lm: rgba(255, 255, 255, 0.35);
--background-dm: rgba(0, 0, 0, 0.15);
--text-color-lm: #4d4d4d;
--text-color-dm: #d1d1d1;
--text-color-active: #ffffff;
}
.active {
background-color: var(--background-active) !important;
color: var(--text-color-active) !important;
}
.with-border {
width: 100%;
height: 100%;
display: flex;
align-items: center;
background: var(--background-lm);
}
.icon-button::before {
text-align: center;
vertical-align: middle;
content: attr(data-icon);
font-family: gaia-icons;
font-style: normal;
text-rendering: optimizelegibility;
font-weight: 500;
}
.icon-base {
justify-content: center;
}
.more-info-icon::after {
text-align: center;
vertical-align: middle;
content: attr(data-icon);
font-family: gaia-icons;
font-style: normal;
text-rendering: optimizelegibility;
font-weight: 500;
}
p {
position: relative;
color: var(--text-color-lm);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: OPPOSans;
font-style: normal;
mix-blend-mode: normal;
}
.active > p {
color: var(--text-color-active);
}
:host([deep-mode]) .with-border {
background: var(--background-dm);
}
:host([deep-mode]) p {
position: relative;
color: var(--text-color-dm);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: OPPOSans;
font-style: normal;
font-weight: 400;
mix-blend-mode: normal;
}
@media screen and (min-width: 600px) {
.with-border {
border-radius: 8px;
}
.icon-button::before {
width: 24px;
height: 24px;
line-height: 24px;
font-size: 24px;
}
.icon-with-state::before {
position: relative;
left: 11px;
}
.more-info-icon {
width: 8px;
height: 8px;
position: relative;
left: 29px;
}
.more-info-icon::after {
width: 8px;
height: 8px;
line-height: 8px;
font-size: 8px;
}
p {
width: 50px;
height: 10px;
left: 17px;
font-weight: 400;
font-size: 10.5px;
line-height: 10px;
}
}
@media screen and (min-width: 900px) {
.with-border {
border-radius: 16px;
}
.icon-button::before {
width: 48px;
height: 48px;
line-height: 48px;
font-size: 48px;
}
.icon-with-state::before {
position: relative;
left: 22px;
}
.more-info-icon {
width: 16px;
height: 16px;
position: relative;
left: 58px;
}
.more-info-icon::after {
width: 16px;
height: 16px;
line-height: 16px;
font-size: 16px;
}
p {
width: 100px;
height: 20px;
left: 34px;
font-size: 21px;
line-height: 20px;
}
}
`
}
declare global {
interface HTMLElementTagNameMap {
'icon-control-bar': IconControlBar
}
}

View File

@ -0,0 +1 @@
export * from './icon-control-bar.js'

View File

@ -0,0 +1,9 @@
export interface WebActivity {
start(): Promise<any>
cancel(): void
}
export var WebActivity: {
prototype: WebActivity
new (name: string, data?: any): WebActivity
}

View File

@ -0,0 +1,22 @@
{
"name": "@star-web-components/icon-control-bar",
"version": "0.0.1",
"description": "",
"type": "module",
"main": "./index.js",
"module": "./index.js",
"exports": {
".": {
"default": "./index.js"
},
"./index": {
"default": "./index.js"
},
"./icon-control-bar.js": {
"default": "./icon-control-bar.js"
},
"./package.json": "./package.json"
},
"author": "",
"license": "ISC"
}

View File

@ -0,0 +1,8 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"rootDir": "../../"
},
"include": ["*.ts", "./interface.js"]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,32 +1,36 @@
# 图案密码 pattern-view # 图案密码 pattern-view
工作职责: 工作职责:
- 由九宫圆圈组成的图案密码 - 由九宫圆圈组成的图案密码
- 默认密码为 `012543` (滑动如下图) - 默认密码为 `012543` (滑动如下图)
```
--- --- ---
| * | **** | * | **** | * |
--- --- ---
*
--- --- ---
| * | **** | * | **** | * |
--- --- ---
--- --- ---
| | | | | |
--- --- ---
``` ```
--- --- ---
| * | **** | * | **** | * |
--- --- ---
*
--- --- ---
| * | **** | * | **** | * |
--- --- ---
--- --- ---
| | | | | |
--- --- ---
```
- 点击数字反馈,输入成功上滑,输入错误抖动反馈 - 点击数字反馈,输入成功上滑,输入错误抖动反馈
### 默认 ### 默认
``` ```
<star-pattern-view></star-pattern-view> <star-pattern-view></star-pattern-view>
``` ```
### 距离顶部的位置 `topDir` 默认`217.5px` ### 距离顶部的位置 `topDir` 默认`217.5px`
``` ```
<star-pattern-view topDir="300px"></star-pattern-view> <star-pattern-view topDir="300px"></star-pattern-view>
<star-pattern-view topDir="-100px"></star-pattern-view> <star-pattern-view topDir="-100px"></star-pattern-view>
``` ```

View File

@ -2,43 +2,49 @@ import {css} from 'lit'
export default css` export default css`
:host { :host {
:host { width: inherit;
width: inherit; display: block;
display: block; margin: 20px auto;
margin: 20px auto; max-width: 88vw;
max-width: 88vw; }
}
ul { /* menu ul */
list-style: none; :host(#iconmenu) {
flex-direction: column; width: 320px;
display: flex; display: block;
width: 100%; margin: 0;
padding: 0; border-radius: 16px;
margin: 10px auto; }
max-width: 88vw;
background: var(--bg-ul, var(--pure-white));
box-shadow: rgb(64 60 67 / 16%) 1px 1px 5px 1px;
border-radius: inherit; /* 外部传入 */
}
header, ul {
footer { list-style: none;
color: #888; flex-direction: column;
margin-left: 10px; display: flex;
font-size: 12px; width: 100%;
} padding: 0;
margin: 10px auto;
max-width: 88vw;
background: var(--bg-ul, var(--pure-white));
box-shadow: rgb(64 60 67 / 16%) 1px 1px 5px 1px;
border-radius: inherit; /* 外部传入 */
}
star-ul-base { header,
margin: 8px 0 12px 0; /* override child(star-ul-base) */ footer {
} color: #888;
margin-left: 10px;
font-size: 12px;
}
footer a { star-ul-base {
text-decoration: unset; margin: 8px 0 12px 0; /* override child(star-ul-base) */
} }
footer a:visited { footer a {
color: blue; text-decoration: unset;
} }
footer a:visited {
color: blue;
} }
` `

View File

@ -16,6 +16,8 @@ import './components/toast/toast'
import './components/picker/picker' import './components/picker/picker'
import './components/overflowmenu/overflowmenu' import './components/overflowmenu/overflowmenu'
import './components/slider/slider' import './components/slider/slider'
import './components/icon-control-bar/icon-control-bar'
import './components/icon-control-bar-group/icon-control-bar-group'
import './components/notification/notification' import './components/notification/notification'
import './components/prompt/prompt' import './components/prompt/prompt'
import './components/digicipher/digicipher' import './components/digicipher/digicipher'

View File

@ -81,16 +81,16 @@ class MyElement extends StarBaseElement {
<center><h2>FSM状态转移表</h2></center> <center><h2>FSM状态转移表</h2></center>
| 当前状态 →<br>条件 ↓ | initialState | touchStartedState | touchesStartedState | holdState | panState | swipeState | pinchState | rotateState | | 当前状态 →<br>条件 ↓ | initialState | touchStartedState | touchesStartedState | holdState | panState | swipeState | transformstate |
| ---------------------------------- | ----------------- | ------------------- | ------------------- | ------------ | ---------- | ------------ | ------------ | ------------ | | ---------------------------------- | ----------------- | ------------------- | ---------------------- | ------------ | ---------- | ------------ | -------------- |
| 收到 touchstart | touchStartedState | | 收到 touchstart | touchStartedState |
| 收到 touchstart 且触摸点=1 | | initialState | | 收到 touchstart 且触摸点=1 | | initialState |
| 收到 touchstart 且触摸点>1 | | touchesStartedState | | 收到 touchstart 且触摸点>1 | | touchesStartedState |
| 收到 touchstart 且判定进行捏 | | | pinchState | | 收到 touchstart 且判定进行捏 | | | transformstate(pinch) |
| 收到 touchstart 且判定进行旋转 | | | rotateState | | 收到 touchstart 且判定进行旋转 | | | transformstate(rotate) |
| 收到 touchstart 且判定进行平移 | | | panState | | 收到 touchstart 且判定进行平移 | | | panState |
| 收到 touchmove 且移动差值>平移阈值 | | panState | | 收到 touchmove 且移动差值>平移阈值 | | panState |
| 收到 touchend | | initialState | | initialState | swipeState | initialState | initialState | initialState | | 收到 touchend | | initialState | | initialState | swipeState | initialState | initialState |
| hold 超时完成 | | holdState | | hold 超时完成 | | holdState |
## 手势事件(Gesture Events) ## 手势事件(Gesture Events)
@ -224,3 +224,27 @@ Interface RotateEvent {
// }, // },
// }) // })
``` ```
## 注意
```js
/**
* 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
)
```

7
src/lib/gesture/TODO.md Normal file
View File

@ -0,0 +1,7 @@
# TODO
1. 单击位移阈值 10px 或热区以内
2. 双击间隔时间小于 300ms两次点击的手指位移阈值 10px
3. 长按的位移阈值 10px
4. 拖放判定条件:手指进入长按状态后,手指位移阈值 10px
5. 滑动、轻扫判定条件:手指位移阈值 20px

View File

@ -99,8 +99,7 @@ export interface SwipeEvent {
readonly startTouch?: Touch readonly startTouch?: Touch
readonly endTouch?: Touch readonly endTouch?: Touch
readonly velocity: number readonly velocity: number
// readonly direction: number readonly direction?: PanOrSwipeDirection // number
readonly direction?: PanOrSwipeDirection
readonly angle?: number readonly angle?: number
} }
@ -222,8 +221,6 @@ export type GestureState =
| 'panState' | 'panState'
| 'holdState' | 'holdState'
| 'swipeState' | 'swipeState'
| 'rotateState'
| 'pinchState'
| 'transformState' | 'transformState'
type PickUP<T, K extends keyof T> = T[K] type PickUP<T, K extends keyof T> = T[K]
@ -733,31 +730,11 @@ export default class GestureDector {
} }
/** /**
* * Pinch:(pointers:2,threshold:0) *
* - pinchstart
* - pinchmove
* - pinchend
* - pinchcancel
* - pinch(include below all)
* - pinchin
* - pinchout
*/
private pinchState: FSMGestureState = {
name: 'pinchState',
init: (evt, touch) => {
this.pinchState.touchstart?.(evt, touch)
},
touchstart: (_, __) => {
this.emitEvent('pinchstart')
},
touchmove: null,
touchend: null,
touchcancel: null,
}
/**
*
* *
*
*
* 囊括:rotate pinch
* *
*/ */
private transformState: FSMGestureState = { private transformState: FSMGestureState = {
@ -835,8 +812,9 @@ export default class GestureDector {
} }
if (this.scaled === true) { if (this.scaled === true) {
this.switchTo(this.pinchState, evt, touch) // ..
} else if (this.rotated === true) { } else if (this.rotated === true) {
// ..
} }
if (this.scaled === true || this.rotated === true) { if (this.scaled === true || this.rotated === true) {
@ -1124,6 +1102,7 @@ export default class GestureDector {
// 清理资源 // 清理资源
this.lastPanTouchID = -1 this.lastPanTouchID = -1
// 如果检测到是第2指提前退出
if (isSecondFinger) { if (isSecondFinger) {
debugInfo('swipe::isSecondFinger', this.vc) debugInfo('swipe::isSecondFinger', this.vc)
if ( if (
@ -1201,15 +1180,6 @@ export default class GestureDector {
touchcancel: null, touchcancel: null,
} }
// private rotateState: FSMGestureState = {
// name: 'rotateState',
// init: () => {},
// touchstart: null,
// touchmove: null,
// touchend: null,
// touchcancel: null,
// }
// TODO: 优化调用处,添加缓存处理 // TODO: 优化调用处,添加缓存处理
private getSeriesEvent<T extends SeriesOfEventsType>( private getSeriesEvent<T extends SeriesOfEventsType>(
type: T type: T

View File

@ -13,6 +13,9 @@ export class PanelActiveOverlay extends LitElement {
public overlayStack = new OverlayStack() public overlayStack = new OverlayStack()
public test(e: Event) { public test(e: Event) {
this.addEventListener('test', () => {
this.overlayStack.closeOverlay()
})
if (this.overlayStack.isOpen == false) { if (this.overlayStack.isOpen == false) {
// 获取被点击节点 // 获取被点击节点
const targetNode = e.target as HTMLElement const targetNode = e.target as HTMLElement
@ -40,7 +43,7 @@ export class PanelActiveOverlay extends LitElement {
<button></button> <button></button>
</div> </div>
<div style="position: fixed; top: 98%;"> <div style="position: fixed; top: 20%;">
<button id="open" @click="${this.test}">overlay层</button> <button id="open" @click="${this.test}">overlay层</button>
<button></button> <button></button>
</div> </div>
@ -50,7 +53,7 @@ export class PanelActiveOverlay extends LitElement {
<button></button> <button></button>
</div> </div>
<div style="position: fixed; top: 98%; left: 90%;"> <div style="position: fixed; top: 80%; left: 90%;">
<button id="open" @click="${this.test}">overlay层</button> <button id="open" @click="${this.test}">overlay层</button>
<button></button> <button></button>
</div> </div>

View File

@ -0,0 +1,129 @@
import {html, LitElement} from 'lit'
import {customElement, query} from 'lit/decorators.js'
import {IconControlBarType} from '../../../components/icon-control-bar/icon-control-bar'
import '../../../components/icon-control-bar/icon-control-bar'
import {IconControlBarGroupType} from '../../../components/icon-control-bar-group/icon-control-bar-group'
import {UlType} from '../../../components/ul/ul'
import {HeaderBarType} from '../../../components/header-bar/header-bar'
import '../../../components/icon-control-bar-group/icon-control-bar-group'
import '../../../components/ul/ul'
import '../../../components/header-bar/header-bar'
@customElement('panel-control-center')
export class PanelControlCenter extends LitElement {
@query('icon-control-bar[icon="wifi-4"]') wifiIcon!: LitElement
@query('icon-control-bar[icon="crop"]') cropBtn!: LitElement
@query('icon-control-bar[icon="brightness"]') brightnessBtn!: LitElement
render() {
return html`
<star-ul type=${UlType.ONLY_HEADER} title="icon-control-bar">
<p>settingsObserver监测值</p>
<icon-control-bar
type=${IconControlBarType.WITH_STATE}
icon="wifi-4"
bgchange="true"
settingsKey="wifi.enabled"
stateDesc="fgfgfhhhh"
@click=${this}
></icon-control-bar>
<br />
<p></p>
<icon-control-bar
type=${IconControlBarType.BASE}
icon="crop"
@click=${this}
></icon-control-bar>
<br />
<p></p>
<icon-control-bar
type=${IconControlBarType.BASE}
icon="brightness"
bgchange="true"
settingsKey="screen.automatic-brightness"
@click=${this}
></icon-control-bar>
<br />
<p></p>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="settings"
></icon-control-bar>
</star-ul>
<star-ul type=${UlType.ONLY_HEADER} title="icon-control-bar-group">
<p></p>
<icon-control-bar-group type=${IconControlBarGroupType.WITH_STATE}>
<icon-control-bar
type=${IconControlBarType.WITH_STATE}
icon="wifi-4"
bgchange="true"
settingsKey="wifi.enabled"
stateDesc=""
></icon-control-bar>
<icon-control-bar
type=${IconControlBarType.WITH_STATE}
icon="bluetooth"
bgchange="true"
settingsKey="bluetooth.enabled"
></icon-control-bar>
<icon-control-bar
type=${IconControlBarType.WITH_STATE}
icon="data"
bgchange="true"
settingsKey="a.enabled"
></icon-control-bar>
</icon-control-bar-group>
<br />
<br />
<p>box框线</p>
<icon-control-bar-group type=${IconControlBarGroupType.BASE} count="4">
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="crop"
></icon-control-bar>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="video"
></icon-control-bar>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="camera"
></icon-control-bar>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="o"
></icon-control-bar>
</icon-control-bar-group>
<br />
</star-ul>
<star-ul type=${UlType.ONLY_HEADER} title="header-bar">
<p>header带时间</p>
<header-bar type=${HeaderBarType.ONLY_TIME}>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="compose"
></icon-control-bar>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="settings"
></icon-control-bar>
</header-bar>
<br />
<p>header带时间//</p>
<header-bar type=${HeaderBarType.DATE_TIME}>
<icon-control-bar
type=${IconControlBarType.BASE_WITHOUT_BORDER}
icon="settings"
></icon-control-bar>
</header-bar>
<br />
</star-ul>
`
}
}
declare global {
interface HTMLElementTagNameMap {
'panel-control-center': PanelControlCenter
}
}

View File

@ -1,32 +1,35 @@
import {html, css, LitElement} from 'lit' import {html, css, LitElement} from 'lit'
import {customElement, query, state} from 'lit/decorators.js' import {customElement, query} from 'lit/decorators.js'
import '../../../components/grabber/home-bar-indicator' import '../../../components/grabber/home-bar-indicator'
import {HomeBarIndicator} from '../../../components/grabber/home-bar-indicator' import {HomeBarIndicator} from '../../../components/grabber/home-bar-indicator'
@customElement('panel-home-indicator') @customElement('panel-home-indicator')
export class PanelHomeIndicator extends LitElement { export class PanelHomeIndicator extends LitElement {
@query('home-indicator', true) homeIndicator!: HomeBarIndicator @query('home-indicator', true) homeIndicator!: HomeBarIndicator
// @property({type: String}) status = this.homeIndicator?.status
foo = ''
@state() handleEvent(e: Event) {
bar = '' switch (e.type) {
case 'panmove':
attributeChangedCallback(name: string, _old: string, value: string): void { console.log(e.type)
super.attributeChangedCallback(name, _old, value) break
console.log(name) default:
} console.log(e.type)
}
constructor() {
super()
// setInterval(() => {
// console.log(this.homeIndicator.movePoint)
// }, 30)
} }
render() { render() {
return html` return html`
<home-bar-indicator></home-bar-indicator> <home-bar-indicator
@swipeup=${this}
@swipedown=${this}
@swiperight=${this}
@swipeleft=${this}
@panup=${this}
@pandown=${this}
@panright=${this}
@panleft=${this}
@panmove=${this}
></home-bar-indicator>
` `
} }

View File

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

View File

@ -28,6 +28,7 @@ import './pattern-view/pattern-view'
import './container/homescreen-container' import './container/homescreen-container'
import './toast/toast' import './toast/toast'
import './picker/picker' import './picker/picker'
import './control-center/control-center'
import './notification/notification' import './notification/notification'
import './switch/switch' import './switch/switch'
@ -201,6 +202,14 @@ export class PanelRoot extends LitElement {
href="#overflowmenu" href="#overflowmenu"
></star-li> ></star-li>
<hr /> <hr />
<star-li
type=${LiType.ICON_LABEL}
label="control-center"
icon="play-circle"
iconcolor="blue"
href="#control-center"
></star-li>
<hr />
<star-li <star-li
type=${LiType.ICON_LABEL} type=${LiType.ICON_LABEL}
label="notification" label="notification"
@ -241,14 +250,6 @@ 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="毛玻璃"
@ -299,7 +300,7 @@ export class PanelRoot extends LitElement {
></star-li> ></star-li>
</star-ul> </star-ul>
<star-ul type=${UlType.ONLY_HEADER} title="手势框架"> <star-ul type=${UlType.ONLY_HEADER} title="手势">
<star-li <star-li
type=${LiType.ICON_LABEL} type=${LiType.ICON_LABEL}
label="手势框架" label="手势框架"
@ -307,6 +308,14 @@ export class PanelRoot extends LitElement {
iconcolor="red" iconcolor="red"
href="#gesture" href="#gesture"
></star-li> ></star-li>
<hr />
<star-li
type=${LiType.ICON_LABEL}
label="home指示器"
icon="accessibility"
iconcolor="blue"
href="#home-indicator"
></star-li>
</star-ul> </star-ul>
<star-ul type=${UlType.BASE}> <star-ul type=${UlType.BASE}>