web_ppt / frontend /src /components /NumberInput.vue
CatPtain's picture
Upload 339 files
89ce340 verified
<template>
<div
class="number-input"
:class="{
'disabled': disabled,
'focused': focused,
}"
>
<span class="prefix">
<slot name="prefix"></slot>
</span>
<div class="input-wrap">
<input
type="text"
:disabled="disabled"
v-model="number"
:placeholder="placeholder"
@input="$event => emit('input', $event)"
@focus="$event => handleFocus($event)"
@blur="$event => handleBlur($event)"
@change="$event => emit('change', $event)"
@keydown.enter="$event => handleEnter($event)"
/>
<div class="handlers">
<span class="handler" @click="number += step">
<svg fill="currentColor" width="1em" height="1em" viewBox="64 64 896 896"><path d="M890.5 755.3L537.9 269.2c-12.8-17.6-39-17.6-51.7 0L133.5 755.3A8 8 0 00140 768h75c5.1 0 9.9-2.5 12.9-6.6L512 369.8l284.1 391.6c3 4.1 7.8 6.6 12.9 6.6h75c6.5 0 10.3-7.4 6.5-12.7z"></path></svg>
</span>
<span class="handler" @click="number -= step">
<svg fill="currentColor" width="1em" height="1em" viewBox="64 64 896 896"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg>
</span>
</div>
</div>
<span class="suffix">
<slot name="suffix"></slot>
</span>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
const props = withDefaults(defineProps<{
value: number
disabled?: boolean
placeholder?: string
min?: number
max?: number
step?: number
}>(), {
disabled: false,
placeholder: '',
min: 0,
max: Infinity,
step: 1,
})
const emit = defineEmits<{
(event: 'update:value', payload: number): void
(event: 'input', payload: Event): void
(event: 'change', payload: Event): void
(event: 'blur', payload: Event): void
(event: 'focus', payload: Event): void
(event: 'enter', payload: Event): void
}>()
const number = ref(0)
const focused = ref(false)
watch(() => props.value, () => {
if (props.value !== number.value) {
number.value = props.value
}
}, {
immediate: true,
})
watch(number, () => {
const value = +number.value
if (isNaN(value)) return
else if (value > props.max) return
else if (value < props.min) return
number.value = value
emit('update:value', number.value)
})
const checkAndEmitValue = () => {
let value = +number.value
if (isNaN(value)) value = props.min
else if (value > props.max) value = props.max
else if (value < props.min) value = props.min
number.value = value
emit('update:value', number.value)
}
const handleEnter = (e: Event) => {
checkAndEmitValue()
emit('enter', e)
}
const handleBlur = (e: Event) => {
checkAndEmitValue()
focused.value = false
emit('blur', e)
}
const handleFocus = (e: Event) => {
focused.value = true
emit('focus', e)
}
</script>
<style lang="scss" scoped>
.number-input {
background-color: #fff;
border: 1px solid #d9d9d9;
padding: 0 0 0 5px;
border-radius: $borderRadius;
transition: border-color .25s;
font-size: 13px;
display: inline-flex;
.input-wrap {
flex: 1;
color: $textColor;
padding: 0 0 0 5px;
position: relative;
}
&:not(.disabled) .input-wrap:hover .handlers {
opacity: 1;
}
.handlers {
width: 20px;
position: absolute;
top: 0;
bottom: 0;
right: 0;
display: flex;
flex-direction: column;
font-size: 6px;
color: #999;
opacity: 0;
user-select: none;
transition: opacity .25s;
.handler {
width: 100%;
height: 50%;
display: flex;
justify-content: center;
align-items: center;
border-left: 1px solid #d9d9d9;
cursor: pointer;
& + .handler {
border-top: 1px solid #d9d9d9;
}
&:hover {
color: $themeColor;
}
}
}
input {
width: 100%;
min-width: 0;
padding: 0;
height: 30px;
line-height: 30px;
outline: 0;
border: 0;
font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,'Noto Sans',sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol','Noto Color Emoji';
&::placeholder {
color: #bfbfbf;
}
}
&:not(.disabled):hover, &.focused {
border-color: $themeColor;
}
&.disabled {
background-color: #f5f5f5;
border-color: #dcdcdc;
color: #b7b7b7;
input {
color: #b7b7b7;
}
}
.prefix, .suffix {
display: flex;
justify-content: center;
align-items: center;
line-height: 30px;
user-select: none;
}
}
</style>