Merge pull request #98 in YR/star-web-components from feature-component-datepicker to master

* commit '432e860d283e28e6e422eb6ebd24f48a8e96e382':
  修改返回值类型及对应的自定义属性定义
  修改大小月判断条件错误的bug
  TASK: #113920-Feature component datepicker
This commit is contained in:
汪昌棋 2022-10-24 10:25:21 +08:00
commit 6adaaf37eb
7 changed files with 1683 additions and 410 deletions

View File

@ -51,3 +51,15 @@
<star-picker-option value="3">blue</star-picker-option>
</star-picker>
```
# PickerBase
包含正常的picker选择器以及时间选择器可分为只有时间、只有日期和时间日期都有三种显示
效果
- 伪3D显示三种日期选择:
1只有日期年月日(yyyy-mm-dd)
2只有时间时分秒(h-m-s)
3日期和时间年月日时分秒(yyyy-mm-dd-h-m-s)

View File

@ -0,0 +1,416 @@
import { css } from "lit";
export default css`
.p-scroll{
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
font-family: -apple-system,Helvetica Neue,Helvetica,Arial,sans-serif;
-webkit-tap-highlight-color: rgba(0,0,0,0);
font-size: 14px;
z-index: 9;
}
.p-select-mask{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
cursor: pointer;
background: rgba(0,0,0,.7);
-webkit-animation: p-scroll-fade-in .3s ease both;
}
.p-scroll.hide .p-select-mask{
-webkit-animation: p-scroll-fade-out .3s ease both;
}
.p-select-wrap{
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: -webkit-flex;
-webkit-align-items: center;
-webkit-justify-content: center;
}
.p-select-main{
display: -webkit-flex;
-webkit-flex-direction: column;
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
background: #fff;
box-shadow: 0 -5px 10px rgba(0,0,0,.1);
-webkit-animation: p-scroll-in .3s ease both;
}
.p-scroll.hide .p-select-main{
-webkit-animation: p-scroll-out .3s ease both;
}
@-webkit-keyframes p-scroll-in {
0%{ -webkit-transform: translate3d(0,100%,0);}
100%{ -webkit-transform: translate3d(0,0,0);}
}
@-webkit-keyframes p-scroll-out {
0%{ -webkit-transform: translate3d(0,0,0);}
100%{ -webkit-transform: translate3d(0,100%,0);}
}
@-webkit-keyframes p-scroll-fade-in {
0%{ opacity: 0;}
100%{ opacity: 1;}
}
@-webkit-keyframes p-scroll-fade-out {
0%{ opacity: 1;}
100%{ opacity: 0;}
}
.p-select-head{
position: relative;
height: 45px;
line-height: 45px;
display: -webkit-flex;
-webkit-align-items: center;
-webkit-justify-content: space-between;
background: #f2f2f2;
z-index: 5;
}
.p-select-head:before{
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
-webkit-transform: scaleY(.5);
background-color: #d5d5d6;
}
.p-select-title{
-webkit-flex: 1;
font-size: 18px;
color: #333;
text-align: center;
}
.p-select-head a{
display: block;
padding: 0 15px;
height: 45px;
line-height: 45px;
font-size: 18px;
text-decoration: none;
color: #007aff;
}
.p-select-foot{
position: relative;
display: -webkit-flex;
-webkit-align-items: center;
background-color: #fff;
}
.p-select-foot:before{
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 1px;
background-color: #d5d5d6;
-webkit-transform: scaleY(0.5);
}
.p-select-foot a{
position: relative;
display: block;
height: 45px;
line-height: 45px;
font-size: 18px;
text-decoration: none;
color: #007aff;
-webkit-flex: 1;
text-align: center;
}
.p-select-foot a:before{
content: "";
position: absolute;
top: 0;
right: 0;
height: 100%;
width: 1px;
background-color: #d5d5d6;
-webkit-transform: scaleX(0.5);
}
.p-select-foot a:last-child:before{
display: none;
}
.p-select-body{
position: relative;
margin: 20px auto;
width: 100%;
display: -webkit-flex;
-webkit-align-items: center;
-webkit-justify-content: center;
overflow: hidden;
box-sizing: border-box;
}
.p-select-body ul{
list-style-type: none;
}
.p-select-item {
position: relative;
display: -webkit-flex;
-webkit-align-items: center;
height: 170px;
text-align: center;
/* overflow: hidden; */
}
.p-select-col{
height: 100%;
position: relative;
}
.p-select-ul {
margin: 0;
padding: 0;
position: relative;
}
.p-select-list,
.p-select-wheel,
.p-select-line{
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 34px;
margin-top: -17px;
box-sizing: border-box;
-webkit-transition: all .3s;
}
.p-select-wheel {
padding: 0;
-webkit-transform-style: preserve-3d;
height: 34px;
z-index: 1;
}
.p-select-list{
position: relative;
top: 68px;
margin-top: 0;
z-index: 45;
}
.p-select-line {
height: 34px;
z-index: 100;
pointer-events: none;
box-sizing: border-box;
}
.p-select-line:before,
.p-select-line:after{
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
-webkit-transform: scaleY(.5);
background: #a5a5a5;
}
.p-select-line:after{
top: auto;
bottom: 0;
}
.p-select-label{
margin: 0 -5px;
min-width: 10px;
display: -webkit-flex;
-webkit-align-items: center;
-webkit-justify-content: center;
box-sizing: border-box;
white-space: nowrap;
z-index: 100;
color: #666;
}
.p-select-label span{
display: block;
position: relative;
top: -3px;
font-size: 22px;
font-weight: normal;
text-align: center;
z-index: 50;
}
.p-select-ul>li,
.p-select-wheel>li{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
padding: 0 10px;
line-height: 34px;
font-size: 21px;
box-sizing: border-box;
cursor: pointer;
-webkit-user-select: none;
text-align: center;
}
.p-select-ul>li{
color: #666;
font-size: 22px;
}
.p-select-wheel>li {
-webkit-backface-visibility: hidden;
position: absolute;
top: 0;
color: #999;
}
.p-select-wheel>li:active{
/* opacity: .7;*/
}
.p-select-wheel>li.visible{
display: list-item;
}
.p-select-time-split-1{
position: relative;
top: 0;
right: 0;
bottom: 15px;
margin: 0 15px;
width: 1px;
height: 160px;
-webkit-transform: scaleX(.7) translateZ(200px);
background: -webkit-linear-gradient(90deg,#fff,#ccc 15%,#ccc 85%,#fff);
}
/*is3d*/
.p-select-wrap.p-3d .p-select-body{
-webkit-perspective: 1200;
}
.p-select-wrap.p-3d .p-select-list{
height: 34px;
overflow:hidden;
background-color: #fff;
}
.p-select-wrap.p-3d .p-select-ul>li,
.p-select-wrap.p-3d .p-select-label{
color: #2a2b2c;
}
.p-select-wrap.p-3d .p-select-body:before{
content: "";
position: absolute;
top: -15px;
bottom: -15px;
left: 0;
right: 0;
background: -webkit-linear-gradient(#fff,rgba(255,255,255,0) 52%,rgba(2555,255,255,0) 48%,#fff);
pointer-events: none;
-webkit-transform: translate3d(0,0,120px);
z-index: 5;
}
.p-select-wrap.p-3d .p-select-line:before{
top: 1px;
}
.p-select-wrap.p-3d .p-select-line:after{
bottom: 1px;
}
.p-select-wrap.dark .p-select-main,
.p-select-wrap.dark .p-select-body{
background-color: #080808;
}
.p-select-wrap.dark .p-select-body:before{
background: -webkit-linear-gradient(#080808,rgba(8,8,8,0) 52%,rgba(8,8,8,0) 48%,#080808);
}
.p-select-wrap.dark .p-select-head{
background-color: #1c1c1c;
border-bottom: none;
}
.p-select-wrap.dark .p-select-head:before{
background-color: transparent;
}
.p-select-wrap.dark .p-select-head a{
color: #ff8400;
}
.p-select-wrap.dark .p-select-list,
.p-select-wrap.dark .p-select-ul>li,
.p-select-wrap.dark .p-select-wheel>li{
background-color: #080808;
}
.p-select-wrap.dark .p-select-line:before,
.p-select-wrap.dark .p-select-line:after{
background-color: #444;
}
.p-select-wrap.dark .p-select-time-split-1{
background: -webkit-linear-gradient(90deg,transparent,#333 15%,#333 85%,transparent);
}
.p-select-wrap.dark .p-select-title,
.p-select-wrap.dark .p-select-label,
.p-select-wrap.dark .p-select-ul>li{
color: #fff;
}
.p-select-wrap.dark .p-select-foot{
border-top: none;
background-color: #444;
}
.p-select-wrap.dark .p-select-foot a{
color: #ff8400;
}
.p-select-wrap.dark .p-select-foot:before,
.p-select-wrap.dark .p-select-foot a:before{
background-color: #444;
}
/*center*/
.p-center{
position: relative;
top: auto;
left: auto;
-webkit-transform: none;
width: auto;
}
.p-center .p-select-head{
background-color: #f2f2f2;
}
.p-center .p-select-body{
min-width: 300px;
padding: 0 25px;
box-sizing: border-box;
background-color: #fff;
}
.p-center .p-select-body:before{
background: -webkit-linear-gradient(#f7f7f7,rgba(245,245,245,0) 52%,rgba(245,245,245,0) 48%,#f7f7f7);
}
.p-center .p-select-main{
position: relative;
margin: auto;
bottom: auto;
max-width: 400px;
border-radius: 10px;
overflow: hidden;
-webkit-animation: p-scroll-fade-in .3s ease both;
}
.p-scroll.hide .p-center .p-select-main{
-webkit-animation: p-scroll-fade-out .3s ease both;
}
.p-center .p-select-line{
border-color: #ccc;
}
.p-center .p-select-list{
background-color: #fff;
}
/*center dark*/
.p-center.dark .p-select-wrap.dark{
border: none;
}
.p-center.dark .p-select-head{
background-color: #000;
}
.p-center.dark .p-select-head:before{
background-color: #444;
}
.p-center.dark .p-select-foot{
background-color: #000;
}
`

File diff suppressed because it is too large Load Diff

View File

@ -1,156 +0,0 @@
import {css, CSSResult} from 'lit'
export const timepickerStyles: CSSResult = css`
:host {
display: block;
}
* {
margin: 0;
padding: 0;
}
.btn {
height: 32px;
padding: 0 15px;
text-align: center;
font-size: 14px;
line-height: 32px;
color: #1890ff;
border: none;
background: #fff;
border-radius: 2px;
cursor: pointer;
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
background: rgba(0, 0, 0, 0.6);
animation: fadeIn 0.3s forwards;
}
.slide-box {
position: fixed;
left: 0;
right: 0;
bottom: 30%;
padding: 15px;
border-radius: 20px;
background: #fff;
user-select: none;
text-align: center;
}
.fade-in {
animation: fadeIn 0.3s forwards;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-out {
animation: fadeOut 0.3s forwards;
}
@keyframes fadeOut {
from {
opacity: 10;
}
to {
opacity: 0;
}
}
.slide-up {
animation: slideUp 0.3s forwards;
}
@keyframes slideUp {
from {
transform: translate3d(0, 100%, 0);
}
to {
transform: translate3d(0, 0, 0);
}
}
.slide-down {
animation: slideDown 0.3s forwards;
}
@keyframes slideDown {
from {
transform: translate3d(0, 0, 0);
}
to {
transform: translate3d(0, 100%, 0);
}
}
h4 {
height: 24px;
margin-bottom: 16px;
font-size: 16px;
line-height: 24px;
text-align: center;
}
.picker-group {
display: flex;
}
.picker-column {
position: relative;
flex: 1;
height: 200px;
margin: 0 auto;
overflow: hidden;
touch-action: none;
}
.picker-column::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 1;
height: 79px;
border-bottom: 1px solid #ebebeb;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.9),
rgba(255, 255, 255, 0.6)
);
}
.picker-column::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
z-index: 1;
height: 79px;
border-top: 1px solid #ebebeb;
background: linear-gradient(
to bottom,
rgba(255, 255, 255, 0.6),
rgba(255, 255, 255, 0.9)
);
}
li {
list-style: none;
font-size: 14px;
line-height: 40px;
text-align: center;
}
.picker_label {
margin: 15px auto 0;
}
.btn-cancel {
display: block;
margin: 15px auto 0;
float: left
}
.btn-sure {
display: block;
margin: 15px auto 0;
float: right;
}
`

View File

@ -1,250 +0,0 @@
import {LitElement, CSSResultArray, html} from 'lit'
import {customElement} from 'lit/decorators.js'
import {timepickerStyles} from './timepicker.css'
export class Picker {
public options: any
public isPointerdown: boolean
public itemHeight: number
public maxY: number
public minY: number
public lastY: number
public diffY: number
public translateY: number
public friction: number
public distanceY: number
public result: any
constructor(options: any) {
this.options = Object.assign({}, options)
this.isPointerdown = false
this.itemHeight = 40 // 列表项高度
this.maxY = this.itemHeight * 2
this.minY = this.itemHeight * (3 - this.options.list.length)
this.lastY = 0
this.diffY = 0
this.translateY = 0 // 当前位置
this.friction = 0.95 // 摩擦系数
this.distanceY = 0 // 滑动距离
this.result = this.options.list[0]
this.render()
this.bindEventListener()
}
render() {
let html = ''
for (const item of this.options.list) {
html += '<li>' + item + '</li>'
}
this.options.pickerContent.innerHTML = html
this.options.pickerContent.style.transform =
'translate3d(0px, ' + this.maxY + 'px, 0px)'
}
handlePointerdown(e: PointerEvent) {
// 如果是鼠标点击,只响应左键
if (e.pointerType === 'mouse' && e.button !== 0) {
return
}
this.options.pickerColumn.setPointerCapture(e.pointerId)
this.isPointerdown = true
this.lastY = e.clientY
this.diffY = 0
this.distanceY = 0
this.getTransform()
this.options.pickerContent.style.transform =
'translate3d(0px, ' + this.translateY + 'px, 0px)'
this.options.pickerContent.style.transition = 'none'
}
handlePointermove(e: PointerEvent) {
if (this.isPointerdown) {
this.diffY = e.clientY - this.lastY
this.translateY += this.diffY
this.lastY = e.clientY
this.options.pickerContent.style.transform =
'translate3d(0px, ' + this.translateY + 'px, 0px)'
}
}
handlePointerup() {
if (this.isPointerdown) {
this.isPointerdown = false
this.getTranslateY()
// 滑动距离与时长成正比且最短时长为300ms
const duration = Math.max(Math.abs(this.distanceY) * 1.5, 300)
this.options.pickerContent.style.transition =
'transform ' + duration + 'ms ease'
this.options.pickerContent.style.transform =
'translate3d(0px, ' + this.translateY + 'px, 0px)'
}
}
handlePointercancel() {
if (this.isPointerdown) {
this.isPointerdown = false
}
}
bindEventListener() {
this.handlePointerdown = this.handlePointerdown.bind(this)
this.handlePointermove = this.handlePointermove.bind(this)
this.handlePointerup = this.handlePointerup.bind(this)
this.handlePointercancel = this.handlePointercancel.bind(this)
this.options.pickerColumn.addEventListener(
'pointerdown',
this.handlePointerdown
)
this.options.pickerColumn.addEventListener(
'pointermove',
this.handlePointermove
)
this.options.pickerColumn.addEventListener(
'pointerup',
this.handlePointerup
)
this.options.pickerColumn.addEventListener(
'pointercancel',
this.handlePointercancel
)
}
getTransform() {
const transform = window
.getComputedStyle(this.options.pickerContent)
.getPropertyValue('transform')
this.translateY = parseFloat(transform.split(',')[5])
}
getTranslateY() {
let speed = this.diffY
while (Math.abs(speed) > 1) {
speed *= this.friction
this.distanceY += speed
}
// 边界判断
let y = this.translateY + this.distanceY
if (y > this.maxY) {
this.translateY = this.maxY
this.distanceY = this.maxY - this.translateY
} else if (y < this.minY) {
this.translateY = this.minY
this.distanceY = this.minY - this.translateY
} else {
this.translateY = y
}
// 计算停止位置使其为itemHeight的整数倍
let i = Math.round(this.translateY / this.itemHeight)
this.translateY = i * this.itemHeight
this.result = this.options.list[2 - this.translateY / this.itemHeight]
}
}
@customElement('star-timepicker')
export class TimePicker extends LitElement {
public static override get styles(): CSSResultArray {
return [timepickerStyles]
}
public openState:number = 0
// test
protected firstUpdated() {
const btnOpen = this.shadowRoot!.querySelector('.btn-open') as HTMLElement
const btnSure = this.shadowRoot!.querySelector('.btn-sure') as HTMLElement
const btnCancel = this.shadowRoot!.querySelector(
'.btn-cancel'
) as HTMLElement
const mask = this.shadowRoot!.querySelector('.mask') as HTMLElement
const slide = this.shadowRoot!.querySelector('.slide-box') as HTMLElement
if(this.openState==1){
}
btnOpen.addEventListener('click', function () {
mask.hidden = false
mask.classList.add('fade-in')
slide.classList.add('slide-up')
}, true)
// 点击非选择器区域关闭选择器
// mask.addEventListener('click', function (e) {
// let elm = e.target as HTMLElement
// if (elm.className.includes('mask')) {
// mask.classList.remove('fade-in')
// mask.classList.add('fade-out')
// slide.classList.remove('slide-up')
// slide.classList.add('slide-down')
// }
// })
btnCancel.addEventListener('click', function (e) {
let elm = e.target as HTMLElement
if (elm.className.includes('btn-cancel')) {
mask.classList.remove('fade-in')
mask.classList.add('fade-out')
slide.classList.remove('slide-up')
slide.classList.add('slide-down')
}
}, true)
mask.addEventListener('animationend', function (e) {
let elm = e.target as HTMLElement
if (elm.className.includes('fade-out')) {
mask.hidden = true
mask.classList.remove('fade-out')
slide.classList.remove('slide-down')
}
}, true)
btnSure.addEventListener('click', function (e) {
var a = new Date()
alert(hourPicker.result + ' : ' + minutePicker.result + a)
e.preventDefault()
}, true)
// 调用方式
function createList(start: number, end: number) {
const list = []
for (let i = start; i < end; i++) {
list[i] = i < 10 ? '0' + i : '' + i
}
return list
}
const hours = createList(0, 24),
minutes = createList(0, 60)
const pickerColumns = this.shadowRoot!.querySelectorAll('.picker-column')
const pickerContents = this.shadowRoot!.querySelectorAll('.picker-content')
const hourPicker = new Picker({
pickerColumn: pickerColumns[0],
pickerContent: pickerContents[0],
list: hours,
})
const minutePicker = new Picker({
pickerColumn: pickerColumns[1],
pickerContent: pickerContents[1],
list: minutes,
})
}
render() {
return html`
<button
class="btn btn-open"
type="button"
>
</button>
<div hidden class="mask">
<div class="slide-box">
<div id="checkbar">
<button class="btn btn-cancel" type="button"></button>
<span class="picker_label" style="display: inline-block">
</span>
<button class="btn btn-sure" type="button"></button>
</div>
<div class="picker-group">
<div class="picker-column">
<ul class="picker-content"></ul>
</div>
<div class="picker-column">
<ul class="picker-content"></ul>
</div>
</div>
</div>
</div>
`
}
}
declare global {
interface HTMLElementTagNameMap {
'star-timepicker': TimePicker
}
}

View File

@ -26,7 +26,7 @@ import './components/pattern-view/pattern-view'
import './components/overlay/active-overlay'
import './components/battery/battery'
import './components/battery-square/battery-square'
import './components/picker/timepicker'
import './components/picker/datepicker'
@customElement('settings-app')
export class SettingsApp extends LitElement {
@query('star-animate-section#root') private rootSection!: StarAnimateSection

View File

@ -1,7 +1,7 @@
import {html, LitElement, CSSResultArray} from 'lit'
import {customElement} from 'lit/decorators.js'
import {sharedPickerStyles} from '../shared-picker-styles'
import '../../../components/picker/timepicker'
import '../../../components/picker/datepicker'
@customElement('panel-picker')
export class PanelPicker extends LitElement {
@ -72,8 +72,11 @@ export class PanelPicker extends LitElement {
</label>
<label>
<span>datepicker</span>
<star-timepicker></star-timepicker>
<star-pickerbase></star-pickerbase>
</label>
<label>
<star-pickerbase type="datepicker"></star-pickerbase>
</label>
</div>
`