@suyouwanggang/p-ui
Version:
`p-ui`是一套使用原生`Web Components`规范开发的跨框架UI组件库,基于`lit-elment`库开发。 [github项目地址](https://github.com/suyouwanggang/p-ui)
421 lines (407 loc) • 16 kB
text/typescript
import { css, customElement, html, LitElement, property } from 'lit-element';
import { ifDefined } from 'lit-html/directives/if-defined';
import { getValidityResult, getCountDecimals } from './helper/formValidate';
import NP from 'number-precision';
import PButton from './p-button';
import PTips from './p-tips';
type inputtype = 'text' | 'password' | 'email' | 'url' | 'number' | 'tel' | 'search';
class MinInputClass extends LitElement {
public get input(): HTMLInputElement | unknown {
return this;
}
({ type: String, reflect: true }) name?: string;
({ type: String, reflect: false }) value?: string = '';
({ type: Boolean, reflect: true }) invalid?: boolean = false;
({ type: Boolean, reflect: true }) novalidate?: boolean = false;
({ type: Boolean, reflect: true }) required?: boolean = false;
({ type: String, reflect: true }) errorMessage?: string = undefined;
({ type: String, reflect: true }) pattern?: string = undefined;
({ type: Number, reflect: true }) minLength?: number = undefined;
({ type: Number, reflect: true }) maxLength?: number = undefined;
({ type: Number, reflect: true }) min?: number = undefined;
({ type: Number, reflect: true }) max?: number =undefined;
({ type: Number, reflect: true }) step?: number = 1;
({ type: Object, attribute: false }) customValidateMethod?: any = undefined;
get validity(): boolean {
return getValidityResult(this).valid;
}
get validationMessage(): string {
const result = getValidityResult(this);
if (!result.valid) {
const errorMessage = this.errorMessage;
if (errorMessage === null || errorMessage === undefined) {
const array = result.message;
const message = array[0].message;
return message;
} else {
return errorMessage;
}
} else {
return '';
}
}
}
function throttleFunction(fn: Function, delay: number, context: any) {
let previous = 0;
// 使用闭包返回一个函数并且用到闭包函数外面的变量previous
return function () {
const args = [...arguments];
const now: number = + new Date();
if (now - previous > delay) {
fn.apply(context, args);
previous = now;
}
}
}
('p-input')
class PInput extends MinInputClass {
({ type: String, reflect: true }) label?: string;
({ type: String, reflect: true }) tips?: string;
({ type: String, reflect: false }) errortips?: string;
({ type: Boolean, reflect: true }) disabled?: boolean = false;
({ type: Boolean, reflect: true }) readOnly?: boolean = false;
({ type: String, reflect: true }) type?: inputtype = 'text';
({ type: String, reflect: true }) placeholder?: string;
({ type: String, reflect: false }) leftIcon?: string;
({ type: String, reflect: false }) rightIcon?: string;
({ type: Boolean, reflect: true }) block?: boolean = false;
({ type: Boolean, reflect: true }) clear?: boolean = false;
({ type: Number, reflect: true }) debounce?: number = undefined;
({ type: Number, reflect: true }) throttle?: number = undefined;
({ type: Boolean, reflect: true }) showStep?: boolean = false;
static get styles() {
return css`
:host{
display:inline-block;
border:1px solid var(--borderColor,rgba(0,0,0,.2));
border-radius:var(--borderRadius,0.25em);
transition:border-color 0.3s,box-shadow .3s;
color: var(--fontColor,#333);
cursor:text;
}
:host([block]){
display:block;
}
:host([disabled]){
opacity:var(--disabled-opaticity,0.8);
cursor:not-allowed;
}
:host([disabled]) p-tips{
pointer-events:none;
background:rgba(0,0,0,.08);
}
:host([invalid]){
--themeColor:var(--errorColor,#f4615c);
border-color:var(--errorColor,#f4615c);
}
:host(:focus-within:not([disabled])), :host(:not([disabled]):hover){
border-color:var(--themeColor,#42b983);
}
p-tips{
box-sizing:border-box;
display:flex;
align-items:center;
height:100%;
width: 100%;
display: flex;
align-items: center;
padding: 6px 6px;
font-family: inherit;
transition: .3s background-color;
}
p-tips[show=true]{
--color:var(--errorColor,#f4615c);
}
p-tips>input{
text-align: inherit;
color: currentColor;
padding:0;
padding-left:0.3em;
border: 0;
outline: 0;
line-height: inherit;
font-size: inherit;
font-family: inherit;
flex: 1;
min-width: 0;
-webkit-appearance: none;
-moz-appearance: textfield;
background: none;
overflow-x: hidden;
transition: color .3s;
}
input[type="number"]::-webkit-inner-spin-button{
display:none;
}
::-moz-focus-inner,::-moz-focus-outer{
border:0;
outline : 0;
}
input::placeholder{
color:#999;
font-size:90%;
}
p-icon{
display: flex;
}
:host(:focus-within:not([disabled])) .leftIcon{
color:var(--themeColor,#42b983);
}
.eye-icon{
padding:0;
line-height: 1em;
cursor:pointer;
}
:host([disabled]) .eye-icon{
cursor:not-allowed;
}
.btn-number{
display:flex;
flex-direction:column;
margin:-6px 0;
width:1em;
visibility:hidden;
transition:0s;
}
.btn-number p-button{
display: flex;
color: #999;
border-radius:0;
width:100%;
flex:1;
padding:0 2px;
font-size:1em;
transition:.2s;
}
.btn-number p-button:hover{
flex:1.5;
}
:host(:focus-within:not([disabled])) .btn-number, :host(:not([disabled]):hover) .btn-number {
visibility: visible;
}
.input-label{
pointer-events:none;
margin-left:0.14em;
position:absolute;
transition: transform .3s, color .3s, background-color .3s;
transform-origin: left;
color:#999;
}
:host([leftIcon]) .input-label{
margin-left:1.5em;
}
:host([label]) #input::placeholder{
color:transparent;
}
#input:not(:placeholder-shown) + .input-label,
#input:focus + .input-label{
transform: translateY( calc( -50% - 0.5em ) ) scale(0.8);
background:#fff;
}
#input:-moz-ui-invalid{
box-shadow:none;
}
:host(:focus-within:not([disabled])) .icon-pre,
:host(:not([disabled]):hover) .icon-pre,
:host(:not([disabled]):hover) .input-label,
:host(:focus-within:not([disabled])) .input-label{
color:var(--themeColor,#42b983);
}
`;
}
public checkValidity() {
if (this.novalidate || this.disabled || this.form && this.form.novalidate) {
return true;
}
if (this.validity) {
this.pTipCon.show = 'false';
this.invalid = false;
this.pTipCon.tips = '';
return true;
} else {
this.pTipCon.show = 'true';
this.invalid = true;
this.input.setCustomValidity('');
this.pTipCon.tips = this.validationMessage;
return false;
}
}
get input(): HTMLInputElement {
return this.renderRoot.querySelector('#input');
}
focus() {
this.input!.focus();
}
reset() {
this.value = '';
this.input!.value = '';
this.invalid = false;
this.pTipCon.tips = '';
this.pTipCon.show = 'false';
}
get form(): HTMLFormElement {
return this.closest('form,p-form');
}
private typePassword() {
const btn: PButton = this.renderRoot.querySelector('#eye-icon');
if (this.type === 'password') {
this.type = 'text';
btn.icon = 'eye';
} else {
this.type = 'password';
btn.icon = 'eye-close';
}
}
clearValue() {
this.reset();
}
searchValue() {
this.dispatchEvent(new CustomEvent('submit', {
detail: {
value: this.value
}
}));
}
dispatchChange() {
this.checkValidity();
const changeEvent = new CustomEvent('change', {
detail: { value: this.input.value }
});
}
dispatchFocus() {
const changeEvent = new CustomEvent('focus', {
detail: { value: this.input.value }
});
this.dispatchEvent(changeEvent);
this.checkValidity();
}
_dispatchInput() {
const inputEvent = new CustomEvent('input', {
cancelable: true,
detail: { value: this.input.value }
});
this.dispatchEvent(inputEvent);
}
private static NUMBERINPUTARRAY: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '-'];
private _processInputInvlaide(event: Event) {
this.input.setCustomValidity('');
}
private _processInput(event: InputEvent) {
this.input.setCustomValidity('');
if (this.type === 'number') {
if (event.data && PInput.NUMBERINPUTARRAY.indexOf(event.data) === -1) {
const indexOf = this.input.value.lastIndexOf(event.data);
if (indexOf >= -1) {
this.input.value = this.input.value.substring(0, indexOf);
}
event.preventDefault();
}
if (this.input.value !== '-') {
const v = Number(this.input.value);
if (isNaN(v)) {
this.input.value = '';
}
}
}
if (this.maxLength !== undefined && this.input.value.length > this.maxLength) {
this.input.value = this.input.value.substr(0, this.maxLength);
}
this.value = this.input.value;
this.checkValidity();
const inputEl = this;
event.stopPropagation();
if (this.debounce && this.debounce > 0) {
let timeout: number = (this as any).debounceTimeoutID;
timeout && window.clearTimeout(timeout);
(this as any).debounceTimeoutID = timeout = window.setTimeout(() => {
this._dispatchInput();
}, this.debounce);
} else if (this.throttle && this.throttle > 0) {
let throttleFun = (this as any).throttleFun;
if (throttleFun === undefined) {
throttleFun = throttleFunction(() => {
inputEl._dispatchInput();
}, this.throttle, inputEl);
(this as any).throttleFun = throttleFun;
}
throttleFun();
} else {
this._dispatchInput();
}
}
update(changedProperties: Map<string | number | symbol, unknown>) {
super.update(changedProperties);
if (changedProperties.has('throttle') && this.throttle !== undefined) {
(this as any).throttleFun = undefined;
} else if (changedProperties.has('debounce') && this.debounce !== undefined) {
window.clearTimeout((this as any).debounceTimeoutID);
(this as any).debounceTimeoutID = undefined;
}
}
updated(changedProperties: Map<string | number | symbol, unknown>) {
super.update(changedProperties);
if (changedProperties.has('type')) {
this.pTipCon.show = 'false';
this.pTipCon.tips = '';
this.invalid = false;
if (this.value !== '' && this.type === 'number') {
const n = Number(this.value);
if (isNaN(n)) {
this.value = '';
}
}
}
}
get pTipCon(): PTips {
return this.renderRoot.querySelector('#tips');
}
private firstTypePassword: boolean = false;
firstUpdated() {
if (this.type === 'password') {
this.firstTypePassword = true;
}
}
private _stepAdd() {
if (this.step === undefined) {
this.step = 1;
}
let n = Number(this.value);
if (this.max === undefined || (n + Number(this.step) < this.max)) {
n = NP.plus(n, this.step);
this.value = n.toString();
this.dispatchChange();
}
}
private _stepDel() {
if (this.step === undefined) {
this.step = 1;
}
let n = Number(this.value);
if (this.min === undefined || (n - Number(this.step) >= this.min)) {
n = NP.minus(n, this.step);
this.value = n.toString();
this.dispatchChange();
}
}
private _innerType() {
return this.type;
}
render() {
return html`<p-tips .tips=${this.tips} id="tips" >
${this.leftIcon ? html`<p-icon name='${this.leftIcon}' class='leftIcon' ></p-icon>` : ''}
<input id="input" name="${ifDefined(this.name)}" placeholder="${ifDefined(this.label?this.label:this.placeholder)}" .value="${this.value}" @input="${this._processInput}" @change="${this.dispatchChange}"
?readOnly=${this.readOnly} .type="${this._innerType()}" ?disabled=${this.disabled}
step="${ifDefined(this.step)}" min="${ifDefined(this.min)}" max="${ifDefined(this.max)}" minLength="${ifDefined(this.minLength)}" maxLength="${ifDefined(this.maxLength)}"
@focus=${this.dispatchFocus}
/>
${this.label ? html`<label class='input-label'>${this.label}</label>` : ''}
${this.rightIcon ? html`<p-icon name='${this.rightIcon}' class='rightIcon' ></p-icon>` : ''}
${this.firstTypePassword ? html`<p-button class='eye-icon' id='eye-icon' @click='${this.typePassword}' icon="eye-close" type="flat" shape="circle"></p-button>` : ''}
${this.clear ? html`<p-icon name='close-circle' class='clearIcon' @click=${this.clearValue} ></p-icon>` : ''}
${this.type === 'search' ? html`<p-button icon='search' class='eye-icon' @click=${this.searchValue} type="flat"></p-button>` : ''}
${this.type === 'number' && this.showStep ? html`<div class="btn-right btn-number"><p-button id="btn-add" icon="up" @click="${this._stepAdd}" type="flat" shape="circle"></p-button><p-button id="btn-sub" @click="${this._stepDel}" icon="down" shape="circle" type="flat"></p-button></div>` : ''}
<slot></slot>
</p-tips>`;
}
}
export { PInput };