ui/src/form/number.js

310 lines
6.3 KiB
JavaScript

/**
* {数字文本框组件}
* @author yutent<yutent.io@gmail.com>
* @date 2023/03/06 15:17:25
*/
import { css, html, Component } from 'wkit'
const MAX_VALUE = 1e9 - 1
const MIN_VALUE = -MAX_VALUE
class InputNumber extends Component {
static props = {
max: `num!${MAX_VALUE}`,
min: `num!${MIN_VALUE}`,
step: 'num!1',
value: {
type: Number,
default: 0,
observer(v) {
this.value = this.#fix(v)
}
},
readonly: false,
autofocus: false,
disabled: false,
lazy: 'num!0' // 并发拦截时间, 单位毫秒
}
static styles = [
// 基础样式
css`
:host {
display: inline-flex;
min-width: 128px;
height: 32px;
color: var(--color-dark-1);
border-radius: 3px;
font-size: 14px;
cursor: text;
transition: box-shadow 0.15s linear;
-webkit-user-select: none;
user-select: none;
}
:host(:focus-within) {
box-shadow: 0 0 0 2px var(--color-plain-a);
}
.container {
display: flex;
width: 100%;
height: 100%;
margin: 0 auto;
line-height: 1;
border: 1px solid var(--color-grey-2);
border-radius: inherit;
background: #fff;
color: inherit;
cursor: text;
button {
display: flex;
justify-content: center;
align-items: center;
width: 34px;
height: 100%;
border: 0;
font-size: 18px;
font-family: Tahoma, Verdana, Helvetica, Arial, sans-serif;
background: var(--color-plain-1);
color: inherit;
cursor: pointer;
&:first-child {
border-radius: 2px 0 0 2px;
border-right: 1px solid var(--color-grey-1);
}
&:last-child {
border-radius: 0 2px 2px 0;
border-left: 1px solid var(--color-grey-1);
}
&[disabled] {
cursor: not-allowed;
opacity: 0.6;
}
}
input {
flex: 1;
min-width: 32px;
width: 0;
height: 100%;
padding: 0 6px;
border: 0;
color: inherit;
font-size: inherit;
text-align: center;
background: none;
outline: none;
cursor: inherit;
}
/* ----- */
}
`,
// 尺寸
css`
:host([size='small']) {
min-width: 72px;
height: 24px;
font-size: 12px;
button {
width: 26px;
}
}
:host([round]) {
border-radius: 22px;
button:first-child {
border-radius: 22px 0 0 22px;
}
button:last-child {
border-radius: 0 22px 22px 0;
}
}
`,
// 配色
css`
$colors: (
primary: 'teal',
info: 'blue',
success: 'green',
warning: 'orange',
danger: 'red'
);
@loop $t, $c in $colors {
:host([type='#{$t}']:focus-within) {
box-shadow: 0 0 0 2px var(--color-#{$c}-a);
}
:host([type='#{$t}']) span {
border-color: var(--color-#{$c}-a);
}
:host([type='#{$t}']) .container {
border-color: var(--color-#{$c}-2);
}
}
`,
// 状态
css`
:host([disabled]) {
cursor: not-allowed;
opacity: 0.6;
}
/* --- */
:host([readonly]) .container {
cursor: default;
opacity: 0.8;
span {
cursor: inherit;
}
}
:host([disabled]) .container {
background: var(--color-plain-1);
cursor: not-allowed;
opacity: 0.6;
span {
cursor: inherit;
}
}
`
]
#stamp = 0
mounted() {
if (this.autofocus) {
// 需要focus()才能聚焦成功
this.$refs.input.focus()
}
this.value = this.#fix(this.$refs.input.value)
this.$refs.input.value = this.value
}
#click(ev) {
let { disabled, readOnly } = this
let { act } = ev.target.dataset
if (disabled || readOnly) {
return
}
this.#updateValue(act)
}
#fetch(value) {
let now = Date.now()
let { lazy } = this
this.value = value
// 并发拦截
if (lazy && now - this.#stamp < lazy) {
return
}
this.#stamp = now
this.$emit('input')
this.$emit('change')
}
#updateValue(act) {
let { max, min, step, value } = this
if (act === '+') {
if (value >= max) {
return
}
value += step
} else {
if (value <= min) {
return
}
value -= step
}
this.#fetch(value)
this.$refs.input.value = this.value
}
#changed(ev) {
let _value = +ev.target.value
let value = this.#fix(_value)
if (value !== _value) {
ev.target.value = value
}
if (this.value !== value) {
this.#fetch(value)
}
}
#keydown(ev) {
let now = Date.now()
let { lazy, disabled, readOnly } = this
if (disabled || readOnly) {
return
}
// up: 38, down: 40
if (ev.keyCode === 38 || ev.keyCode === 40) {
ev.preventDefault()
return this.#updateValue(ev.keyCode === 38 ? '+' : '-')
}
// 回车触发submit事件
if (ev.keyCode === 13) {
ev.preventDefault()
// 并发拦截
if (lazy && now - this.#stamp < lazy) {
return
}
this.#stamp = now
this.value = this.#fix(ev.target.value)
this.$refs.input.value = this.value
this.$emit('submit', { value: this.value })
}
}
#fix(val) {
let { max, min } = this
return Math.min(max, Math.max(+val, min))
}
render() {
return html`
<main class="container">
<button
data-act="-"
:disabled=${this.value <= this.min}
@click=${this.#click}
>
-
</button>
<input
ref="input"
maxlength="10"
disabled=${this.disabled}
:readOnly=${this.readOnly}
value=${this.value}
@change=${this.#changed}
@keydown=${this.#keydown}
/>
<button
data-act="+"
:disabled=${this.value >= this.max}
@click=${this.#click}
>
+
</button>
</main>
`
}
}
InputNumber.reg('number')
百搭WCUI组件库, 基于web components开发。面向下一代的UI组件库
JavaScript 98.9%
CSS 1.1%