Skip to content
This repository has been archived by the owner on Jan 8, 2025. It is now read-only.

Commit

Permalink
feat(ui/counter): add component counter
Browse files Browse the repository at this point in the history
  • Loading branch information
ayangweb committed Feb 22, 2022
1 parent 4b14584 commit 20a55df
Show file tree
Hide file tree
Showing 15 changed files with 1,215 additions and 8 deletions.
312 changes: 312 additions & 0 deletions packages/varlet-vue2-ui/src/counter/Counter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
<template>
<div class="var-counter var--box">
<div
class="var-counter__controller var-elevation--2"
:class="[disabled || formDisabled ? 'var-counter--disabled' : null, errorMessage ? 'var-counter--error' : null]"
:style="{ background: color ? color : undefined }"
v-bind="$attrs"
>
<var-icon
class="var-counter__decrement-button"
var-counter-cover
name="minus"
:class="[!decrementButton ? 'var-counter--hidden' : null]"
:style="{
width: toSizeUnit(buttonSize),
height: toSizeUnit(buttonSize),
}"
v-ripple="{
disabled: !ripple || disabled || readonly || disableDecrement || !decrementButton || isMin,
}"
@click="decrement"
@touchstart="pressDecrement"
@touchend="releaseDecrement"
@touchcancel="releaseDecrement"
/>
<input
class="var-counter__input"
:style="{
width: toSizeUnit(inputWidth),
fontSize: toSizeUnit(inputTextSize),
}"
:inputmode="toNumber(decimalLength) === 0 ? 'numeric' : 'decimal'"
:readonly="readonly || formReadonly"
:disabled="disabled || formDisabled || disableInput"
v-model="inputValue"
@change="handleChange"
/>
<var-icon
class="var-counter__increment-button"
var-counter-cover
name="plus"
:class="[!incrementButton ? 'var-counter--hidden' : null]"
:style="{
width: toSizeUnit(buttonSize),
height: toSizeUnit(buttonSize),
}"
v-ripple="{
disabled: !ripple || disabled || readonly || disableIncrement || !incrementButton || isMax,
}"
@click="increment"
@touchstart="pressIncrement"
@touchend="releaseIncrement"
@touchcancel="releaseIncrement"
/>
</div>

<var-form-details :error-message="errorMessage"></var-form-details>
</div>
</template>

<script>
import VarIcon from '../icon'
import VarFormDetails from '../form-details'
import Ripple from '../ripple'
import { defineComponent } from '../utils/create'
import { Decimal } from 'decimal.js'
import { props } from './props'
import { toNumber } from '../utils/shared'
import { toSizeUnit } from '../utils/elements'
import { ValidationMixin } from '../utils/mixins/validation'
import { createChildrenMixin } from '../utils/mixins/relation'
const SPEED = 100
const DELAY = 600
export default defineComponent({
name: 'VarCounter',
components: {
VarIcon,
VarFormDetails,
},
directives: { Ripple },
mixins: [ValidationMixin, createChildrenMixin('form', { parentKey: 'form', childrenKey: 'formItems' })],
inheritAttrs: false,
props,
data: () => ({
inputValue: '',
incrementTimer: null,
decrementTimer: null,
incrementDelayTimer: null,
decrementDelayTimer: null,
}),
computed: {
isMax() {
const { max, value } = this
return max != null && toNumber(value) >= toNumber(max)
},
isMin() {
const { min, value } = this
return min != null && toNumber(value) <= toNumber(min)
},
formDisabled() {
return this.form?.disabled
},
formReadonly() {
return this.form?.readonly
},
},
created() {
this.setNormalizedValue(this.normalizeValue(String(this.value)))
},
watch: {
value(newValue) {
this.setNormalizedValue(this.normalizeValue(String(newValue)))
this.getListeners().onChange?.(toNumber(newValue))
},
},
methods: {
// expose
reset() {
const { min, getListeners } = this
getListeners().onInput?.(min != null ? toNumber(min) : 0)
this.resetValidation()
},
toNumber,
toSizeUnit,
validate() {
this._validate(this.rules, this.value)
},
validateWithTrigger(trigger) {
this.$nextTick(() => {
const { validateTrigger, rules, value } = this
this._validateWithTrigger(validateTrigger, trigger, rules, value)
})
},
normalizeValue(value) {
const { decimalLength, max, min } = this
let num = toNumber(value)
if (max != null && num > toNumber(max)) {
num = toNumber(max)
}
if (min != null && num < toNumber(min)) {
num = toNumber(min)
}
value = String(num)
if (decimalLength != null) {
value = num.toFixed(toNumber(decimalLength))
}
return value
},
handleChange(event) {
const { lazyChange, getListeners, change, validateWithTrigger } = this
const { value } = event.target
const normalizedValue = this.normalizeValue(value)
lazyChange
? getListeners().onBeforeChange?.(toNumber(normalizedValue), change)
: this.setNormalizedValue(normalizedValue)
validateWithTrigger('onInputChange')
},
decrement() {
const { disabled, readonly, disableDecrement, decrementButton, lazyChange, step, value, getListeners } = this
const { onDecrement, onBeforeChange } = getListeners()
if (this.formDisabled || this.formReadonly || disabled || readonly || disableDecrement || !decrementButton) {
return
}
if (this.isMin) {
return
}
const localValue = new Decimal(toNumber(value)).minus(new Decimal(toNumber(step))).toString()
const normalizedValue = this.normalizeValue(localValue)
const normalizedValueNum = toNumber(normalizedValue)
onDecrement?.(normalizedValueNum)
if (lazyChange) {
onBeforeChange?.(normalizedValueNum, this.change)
} else {
this.setNormalizedValue(normalizedValue)
this.validateWithTrigger('onDecrement')
}
},
increment() {
const { disabled, readonly, disableIncrement, incrementButton, lazyChange, step, value, getListeners } = this
const { onIncrement, onBeforeChange } = getListeners()
if (this.formDisabled || this.formReadonly || disabled || readonly || disableIncrement || !incrementButton) {
return
}
if (this.isMax) {
return
}
const localValue = new Decimal(toNumber(value)).plus(new Decimal(toNumber(step))).toString()
const normalizedValue = this.normalizeValue(localValue)
const normalizedValueNum = toNumber(normalizedValue)
onIncrement?.(normalizedValueNum)
if (lazyChange) {
onBeforeChange?.(normalizedValueNum, this.change)
} else {
this.setNormalizedValue(normalizedValue)
this.validateWithTrigger('onIncrement')
}
},
pressDecrement() {
const { press, lazyChange } = this
if (!press || lazyChange) {
return
}
this.decrementDelayTimer = window.setTimeout(() => {
this.continuedDecrement()
}, DELAY)
},
pressIncrement() {
const { press, lazyChange } = this
if (!press || lazyChange) {
return
}
this.incrementDelayTimer = window.setTimeout(() => {
this.continuedIncrement()
}, DELAY)
},
releaseDecrement() {
this.decrementTimer && clearTimeout(this.decrementTimer)
this.decrementDelayTimer && clearTimeout(this.decrementDelayTimer)
},
releaseIncrement() {
this.incrementTimer && clearTimeout(this.incrementTimer)
this.incrementDelayTimer && clearTimeout(this.incrementDelayTimer)
},
continuedIncrement() {
this.incrementTimer = window.setTimeout(() => {
this.increment()
this.continuedIncrement()
}, SPEED)
},
continuedDecrement() {
this.decrementTimer = window.setTimeout(() => {
this.decrement()
this.continuedDecrement()
}, SPEED)
},
setNormalizedValue(normalizedValue) {
this.inputValue = normalizedValue
const normalizedValueNum = toNumber(normalizedValue)
this.getListeners().onInput?.(normalizedValueNum)
},
change(value) {
this.setNormalizedValue(this.normalizeValue(String(value)))
this.validateWithTrigger('onLazyChange')
},
},
})
</script>
<style lang="less">
@import '../styles/common';
@import '../styles/elevation';
@import '../icon/icon';
@import '../form-details/formDetails';
@import '../ripple/ripple';
@import './counter';
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`test counter example 1`] = `
"<div class=\\"var-counter-example\\">
<div class=\\"app-type\\">基本使用</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">设置取值范围</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">设置步长</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">保留小数</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"decimal\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">设置尺寸</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms; width: 36px; height: 36px;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" class=\\"var-counter__input\\" style=\\"width: 50px; font-size: 18px;\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms; width: 36px; height: 36px;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">禁用</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2 var-counter--disabled\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" disabled=\\"disabled\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">只读</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" readonly=\\"readonly\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">异步变更</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"app-type\\">字段校验</div>
<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2\\"><i class=\\"var-icon var-counter__decrement-button var-icon--set var-icon-minus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i> <input inputmode=\\"numeric\\" class=\\"var-counter__input\\"> <i class=\\"var-icon var-counter__increment-button var-icon--set var-icon-plus\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub name=\\"var-form-details\\">
<!---->
</transition-stub>
</div>
<div class=\\"space\\"></div>
</div>"
`;
exports[`test counter validation 1`] = `
"<div class=\\"var-counter var--box\\">
<div class=\\"var-counter__controller var-elevation--2 var-counter--error\\"><i class=\\"var-icon var-icon--set var-icon-minus var-counter__decrement-button\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i><input class=\\"var-counter__input\\" inputmode=\\"numeric\\"><i class=\\"var-icon var-icon--set var-icon-plus var-counter__increment-button\\" style=\\"transition: transform 0ms;\\" var-counter-cover=\\"\\"></i></div>
<transition-stub>
<div class=\\"var-form-details\\">
<div class=\\"var-form-details__message\\">必须大于0</div>
<div class=\\"var-form-details__length\\"></div>
</div>
</transition-stub>
</div>"
`;
Loading

0 comments on commit 20a55df

Please sign in to comment.