UNPKG

@suyouwanggang/p-ui

Version:

`p-ui`是一套使用原生`Web Components`规范开发的跨框架UI组件库,基于`lit-elment`库开发。 [github项目地址](https://github.com/suyouwanggang/p-ui)

557 lines (548 loc) 21.3 kB
import { css, customElement, html, LitElement, property, TemplateResult, query } from 'lit-element'; import './p-button'; import PButton from './p-button'; type selectDateType = 'date' | 'month' | 'year' | 'week'; type selectMode = 'date' | 'month' | 'year'; const toDateObj = (d: string | number | undefined | null) => { const date = d === undefined ? undefined : new Date(d); return date; }; const toDate = (d: string | Date | number | undefined) => { const date = d instanceof Date ? d : new Date(d !== undefined ? d : +new Date()); const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); return [year, month, day]; }; const parseDate = (dateString: string | Date, type: selectDateType = 'date') => { const [year, month, day] = toDate(dateString); let value = ''; switch (type) { case 'date': value = year + '-' + (month + '').padStart(2, '0') + '-' + (day + '').padStart(2, '0'); break; case 'month': value = year + '-' + (month + '').padStart(2, '0'); break; default: value = year + ''; break; } return value; }; @customElement('p-date-panel') export default class PDatePanel extends LitElement { static get styles() { return css` :host{ display:block; } .date-pane{ padding:.8em; } .date-head,.date-week{ display:flex; } .date-switch{ flex:1; margin: 0 .3em; } .date-switch[disabled]{ opacity:1; } p-button { padding: 1px; font-size: inherit; box-sizing: content-box; } .icon{ width:1em; height:1em; fill: currentColor; } .prev,.next{ width: 2.3em; height: 2.3em; transition:.3s; } .prev[hidden],.next[hidden]{ visibility: hidden; opacity:0; } .date-week-item{ flex:1; line-height: 2.4; text-align:center; } .date-body{ display:grid; grid-template-columns: repeat(7, 1fr); grid-gap:.5em; } .date-button{ position:relative; background:none; border: 0; padding: 0; color: var(--fontColor,#333); border-radius: var(--borderRadius,.25em); transition:background-color .3s,color .3s,opacity .3s,border-color .3s,border-radius .3s; display:inline-flex; align-items:center; justify-content: center; font-size: inherit; outline:0; } .date-button::before{ content:''; position:absolute; background:var(--themeColor,#42b983); pointer-events:none; left:0; right:0; top:0; bottom:0; opacity:0; transition:.3s; border: 1px solid transparent; z-index:-1; border-radius:inherit; } .date-button:not([disabled]):not([current]):not([select]):not([selectstart]):not([selectend]):hover, .date-button:not([disabled]):not([current]):not([select]):not([selectstart]):not([selectend]):focus{ color:var(--themeColor,#42b983); } .date-button:not([disabled]):hover::before{ opacity:.1 } .date-button:not([disabled]):focus::before{ opacity:.2 } .date-day-item{ box-sizing:content-box; min-width: 2.3em; min-height: 2.3em; justify-self: center; } .date-button[other]{ opacity:.6; } .date-button[disabled]{ cursor: not-allowed; opacity:.6; background: rgba(0,0,0,.1); /*color:var(--errorColor,#f4615c);*/ } .date-button[now]{ color:var(--themeColor,#42b983); } .date-button[current]{ background: var(--themeBackground,var(--themeColor,#42b983)); color:#fff; } .date-button[select]:not([other]){ color:#fff; background: var(--themeBackground,var(--themeColor,#42b983)); } .date-button[selectstart]:not([other]),.date-button[selectend]:not([other]){ color: #fff; border-color: var(--themeColor,#42b983); background: var(--themeBackground,var(--themeColor,#42b983)); } .date-button[selectstart]:not([other])::after,.date-button[selectend]:not([other])::after{ content:''; position: absolute; width: 0; height: 0; top: 50%; overflow: hidden; border: .3em solid transparent; transform: translate(0, -50%); } .date-button[selectstart]:not([other])::after{ border-left-color: var(--themeColor,#42b983); right: 100%; } .date-button[selectend]:not([other])::after{ border-right-color: var(--themeColor,#42b983); left: 100%; } .date-button[selectstart][selectend]:not([other])::after{ opacity:0; } .date-con{ position:relative; } .date-month,.date-year{ position:absolute; display:grid; left:0; top:.8em; right:0; bottom:0; grid-gap:.5em; } .date-month{ grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(4, 1fr); } .date-year{ grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(5, 1fr); } .date-day-item, .date-month-item, .date-year-item{ display:flex; margin:auto; width: 100%; height: 100%; } .date-mode{ opacity:0; visibility:hidden; z-index:-1; transition:.3s opacity,.3s visibility; } .date-mode.show{ z-index:1; opacity:1; visibility:visible; } :host([range]) .date-button[current]{ background: transparent; color:var(--themeColor,#42b983); border-color:var(--themeColor,#42b983); } `; } @property({ type: String, reflect: true }) type: string; @property({ type: String, reflect: true }) value: string = ''; @property({ type: Boolean, reflect: true }) range: boolean; @property({ type: String, reflect: true }) min: string; @property({ type: String, reflect: true }) max: string; @property({ type: String, reflect: true }) mode: selectMode = 'date'; //是选择时间 还是选择年月 还是选择年 private _initalDated = false; get renderHeaderStr() { const date = this.defaultDateValue; if (this._dateType === 'date') { return date.getFullYear() + '年' + String(date.getMonth() + 1).padStart(2, '0') + '月'; } if (this._dateType === 'month') { return date.getFullYear() + '年'; } else { const nv = date.getFullYear(); const n = parseInt(String(nv / 20)); const year = n * 20; return year.toString().padStart(4, '0') + '年到' + (year + 20).toString().padStart(4, '0') + '年'; } } render() { if (this._initalDated === false) { this.__resetDateValue(); this._initalDated = true; } return html` <div class='date-pane' id='date-pane'> <div class='date-head'> <p-button type="flat" class="prev" @click=${this.prevClick} id="prevButton"> <svg class="icon" viewBox="0 0 1024 1024"><path d="M724 218.3V141c0-6.7-7.7-10.4-12.9-6.3L260.3 486.8c-16.4 12.8-16.4 37.5 0 50.3l450.8 352.1c5.3 4.1 12.9 0.4 12.9-6.3v-77.3c0-4.9-2.3-9.6-6.1-12.6l-360-281 360-281.1c3.8-3 6.1-7.7 6.1-12.6z"></path></svg> </p-button> <p-button type="flat" class="date-switch" @click=${this.dateSwitchClick}>${this.renderHeaderStr}</p-button> <p-button type="flat" class="next" @click=${this.nextClick} id="nextButton"> <svg class="icon" viewBox="0 0 1024 1024"><path d="M765.7 486.8L314.9 134.7c-5.3-4.1-12.9-0.4-12.9 6.3v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1c16.4-12.8 16.4-37.6 0-50.4z"></path></svg> </p-button> </div> <div class='date-con' data-type='date' > <div class="date-mode date-date ${this._dateType === 'date' ? 'show' : ''} " > <div class="date-week"> <span class="date-week-item"></span> <span class="date-week-item"></span> <span class="date-week-item"></span> <span class="date-week-item"></span> <span class="date-week-item"></span> <span class="date-week-item"></span> <span class="date-week-item"></span> </div> <div class='date-body'> ${this.renderDateBody()} </div> </div> ${this.mode === 'date' || this.mode === 'month' ? html` <div class='date-mode date-month ${this._dateType === 'month' ? 'show' : ''} ' > ${this.renderMonthBody()} </div>` : '' } <div class='date-mode date-year ${this._dateType === 'year' ? 'show' : ''} ' > ${this.renderYearBody()} </div> </div> </div>`; } @property() private _dateType: selectDateType = undefined; @property() private _dateYear: number = undefined; @property() private _dateMonth: number = undefined; getMonths() { return ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']; } private renderDateBody(): TemplateResult[] { const current = this.defaultDateValue; const [currentYear, currentMonth, currentDate] = toDate(current); const year = this._dateYear; const month = this._dateMonth; const result = []; let minDate = this.minDate; const maxDate = this.maxDate; const days = PDatePanel.getDays(year, month); for (let i = 0, j = days.length; i < j; i++) { const [_year, _month, _day] = days[i].split('-'); const tempDate = toDateObj(days[i]); minDate = this.minDate; const disabled = (minDate != null && tempDate < minDate) || (maxDate != null && tempDate > maxDate); //console.log(`tempDate=${tempDate.toLocaleDateString()} minDate=${minDate?.toLocaleDateString()} maxDate=${maxDate?.toLocaleDateString()} disabled=${disabled}`) result.push(html`<button class="date-button date-day-item" ?other=${parseInt(_year) !== year || parseInt(_month) !== month} ?current=${parseInt(_year) === currentYear && parseInt(_month) === currentMonth && parseInt(_day) === currentDate} data-date="${days[i]}" ?disabled=${disabled} @click=${this.selectDateClick} >${tempDate.getDate()}</button>`); } return result; } private dateSwitchClick(ev: Event) { if (this._dateType === 'date') { this._dateType = 'month'; } else if (this._dateType === 'month') { this._dateType = 'year'; } } private selectDateClick(ev: Event) { const button = ev.target as HTMLElement; const day = button.dataset.date; const date = new Date(day); this.setDateValue(date); } private selectMonthClick(ev: Event) { const button = ev.target as HTMLElement; const month = parseInt(button.dataset.month); let date = this.defaultDateValue; const year = date.getFullYear(); date.setMonth(month); if (date.getMonth() > month) { const lastDate = new Date(year, date.getMonth(), 0); //月最后一天 date = lastDate; } this.setDateValue(date); // this.updateComplete.then(() => { // this._dateType = this.mode === 'date' ? 'date' : 'month'; // }); } private selectYearClick(ev: Event) { const button = ev.target as HTMLElement; const year = parseInt(button.dataset.year); const date = this.defaultDateValue; date.setFullYear(year); this.setDateValue(date); this.updateComplete.then(() => { this._dateType = this.mode === 'year' ? 'year' : 'month'; }); } /** * 获取当前 年 月 看板 * @param year * @param month */ static getDays(year: number, month: number = 1) { const date = new Date(year, month - 1, 1); const week = date.getDay(); date.setDate(date.getDate() - week); const array: string[] = []; let i = 0; while (i < 42) { array.push(parseDate(date)); date.setDate(date.getDate() + 1); i++; } // console.log(`${array}`); return array; } private setDateValue(d: Date) { const minDate = this.minDate; const maxDate = this.maxDate; if (maxDate != null && d > maxDate) { d = maxDate; } if (minDate != null && d < minDate) { d = minDate; } this.value = parseDate(d, this.mode); this.updateComplete.then(() => { this.dispatchChangeEvent(); }); } dispatchChangeEvent() { const event = new CustomEvent('change', { detail: { value: this.value, date: this.dateValue } }); this.dispatchEvent(event); } private renderMonthBody(): TemplateResult[] { const current = this.defaultDateValue; const year = current.getFullYear(); const minDate = this.minDate; const maxDate = this.maxDate; const currentMonth = current.getMonth(); return this.getMonths().map((value: string, index: number) => { let disabled = minDate != null && (year < minDate.getFullYear() || (minDate.getFullYear() === year && index < minDate.getMonth())); if (!disabled) { disabled = maxDate != null && (year > maxDate.getFullYear() || (maxDate.getFullYear() === year && index > maxDate.getMonth())); } return html`<button class="date-button date-month-item" ?current=${index === currentMonth} data-month="${index}" @click='${this.selectMonthClick}' ?disabled=${disabled} >${value}</button>`; }); } private renderYearBody(): TemplateResult[] { const current = this.defaultDateValue; const nv = current.getFullYear(); const n = parseInt(String(nv / 20)); const year = n * 20; const result = []; const minDate = this.minDate; const maxDate = this.maxDate; for (let i = year, j = year + 20; i < j; i++) { let disabled = minDate != null && (i < minDate.getFullYear()); if (!disabled) { disabled = maxDate != null && (i > maxDate.getFullYear()); } result.push(html`<button class="date-button date-year-item" ?current=${i === nv} data-year="${i}" @click=${this.selectYearClick} ?disabled=${disabled} >${i}</button>`); } return result; } /** * 处理设置 年,月,日,当日超过月最大天数, 则设置为最大天数 * @param year * @param month 自然月 * @param day */ private static _fixedDay(year: number, month: number, day: number) { const len = new Date(year, month + 1, 0).getDate(); day = day > len ? len : day; return new Date(year, month, day); } private prevClick(ev: Event) { const dateType = this._dateType; let date = this.defaultDateValue; const [year, month, day] = toDate(this.defaultDateValue); switch (dateType) { case 'date': date = PDatePanel._fixedDay(year, month - 2, day); break; case 'month': date = PDatePanel._fixedDay(year - 1, month - 1, day); break; case 'year': date = PDatePanel._fixedDay(year - 20, month - 1, day); break; default: break; } this.setDateValue(date); this.updateComplete.then(() => { this._dateType = dateType; //this._fixedPrexAndNextButton(); }); } private nextClick(ev: Event) { const dateType = this._dateType; let date = this.defaultDateValue; const [year, month, day] = toDate(this.defaultDateValue); switch (dateType) { case 'date': date = PDatePanel._fixedDay(year, month, day); break; case 'month': date = PDatePanel._fixedDay(year + 1, month - 1, day); break; case 'year': date = PDatePanel._fixedDay(year + 20, month - 1, day); break; default: break; } this.setDateValue(date); this.updateComplete.then(() => { this._dateType = dateType; //this._fixedPrexAndNextButton(); }); } @query('#prevButton') private _prevButton: PButton; @query('#nextButton') private _nextButton: PButton; private _fixedPrexAndNextButton() { const date = this.defaultDateValue; const dataType = this._dateType; const minDate = this.minDate; const maxDate = this.maxDate; if (minDate != null) { if (dataType === 'date') { this._prevButton.disabled = parseDate(minDate, 'month') >= parseDate(date, 'month'); } else { this._prevButton.disabled = minDate.getFullYear() >= date.getFullYear(); } } if (maxDate != null) { if (dataType === 'date') { this._nextButton.disabled = parseDate(maxDate, 'month') <= parseDate(date, 'month'); } else { this._nextButton.disabled = maxDate.getFullYear() <= date.getFullYear(); } } } get dateValue() { if (this.value === '') { return null; } else { return toDateObj(this.value); } } get defaultDateValue() { let d = this.dateValue; if (d == null || d.toString() === 'Invalid Date') { d = new Date(parseDate(new Date(), 'date')); } return d; } private __resetDateValue() { const d = this.defaultDateValue; this._dateMonth = d.getMonth() + 1; this._dateYear = d.getFullYear(); this._dateType = this.mode; return d; } get maxDate() { return this.max !== undefined ? toDateObj(this.max) : null; } get minDate() { return this.min !== undefined ? toDateObj(this.min) : null; } firstUpdated(changedProperties: Map<string | number | symbol, unknown>) { super.firstUpdated(changedProperties); } update(changedProperties: Map<string | number | symbol, unknown>) { super.update(changedProperties); if (this.isConnected && (changedProperties.has('value') || changedProperties.has('mode'))) { this.__resetDateValue(); if (this.dateValue != null) { this.value = parseDate(this.dateValue, this.mode); } this.updateComplete.then(() => { this.requestUpdate(); this._fixedPrexAndNextButton(); }); } } }