🏠 Home 

InputNumber

自定义数值输入框元素

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @require https://update.greasyfork.org/scripts/432807/1160998/InputNumber.js

/**
* @file 自定义数值输入框元素 InputNumber
* @version 1.1.1.20230313
* @author Laster2800
* @see {@link https://gitee.com/liangjiancang/userscript/tree/master/lib/CustomElements/InputNumber InputNumber}
*/
if (!customElements.get('laster2800-input-number')) {
customElements.define('laster2800-input-number', class InputNumber extends HTMLInputElement {
#arrowTid = false
static get observedAttributes() {
return ['type', 'step']
}
constructor() {
super()
this.#update()
this.addEventListener('input', this.#onInput)
this.addEventListener('blur', this.#onInputDone)
this.addEventListener('keydown', this.#onArrowDown)
this.addEventListener('keyup', this.#onArrowUp)
}
#onInput() {
let val = this.value
const regex = this.min < 0 ? /-?\d+(\.(\d+)?)?|-/ : /\d+(\.(\d+)?)?/
val = regex.exec(val)?.[0] ?? ''
if (val) {
if (val !== '-') {
// input listener 只应处理外极值
if (val.startsWith('-')) {
if (val < this.min) {
val = String(this.min)
}
} else {
if (val > this.max) {
val = String(this.max)
}
}
if (this.digits !== Number.POSITIVE_INFINITY) { // Number#toFixed() 不便于动态精度
if (this.digits > 0) {
const m = new RegExp(String.raw`(.+\.\d{${this.digits}}).+`).exec(val)
if (m) {
val = m[1]
}
} else {
const idx = val.indexOf('.')
if (idx >= 0) {
val = val.slice(0, idx)
}
}
}
}
this.value = val
} else {
this.value = ''
}
}
#onInputDone() {
let val = this.value
if (val === '') {
val = this.defaultValue
}
val = Number.parseFloat(val)
if (!Number.isNaN(val)) {
if (val === 0) {
if (this.allowZero === 'true') {
this.value = '0'
return
} else if (this.allowZero === 'false') {
val = Number.parseFloat(this.defaultValue)
if (val === 0) {
this.value = '0'
} else {
this.value = ''
this.#onInputDone()
}
return
}
}
if (val > this.max) {
val = this.max
} else if (val < this.min) {
val = this.min
}
if (this.digits !== Number.POSITIVE_INFINITY) {
val = String(val)
if (this.digits > 0) {
const m = new RegExp(String.raw`(.+\.\d{${this.digits}}).+`).exec(val)
if (m) {
val = m[1]
}
} else {
const idx = val.indexOf('.')
if (idx >= 0) {
val = val.slice(0, idx)
}
}
}
this.value = val
}
}
/** @param {KeyboardEvent} e */
#onArrowDown(e) {
let move = ({ ArrowUp: 1, ArrowDown: -1 })[e.key]
if (move) {
e.preventDefault()
if (this.#arrowTid) return
this.#arrowTid = setTimeout(() => { this.#arrowTid = null }, 100)
if (e.altKey) {
move *= 0.1
} else if (e.shiftKey) {
move *= 10
} else if (e.ctrlKey) {
move *= 100
}
let val = this.value
if (val === '') {
val = this.defaultValue
}
val = Number.parseFloat(val)
if (Number.isNaN(val)) return
val += move
if (val > this.max) {
val = this.max
} else if (val < this.min) {
val = this.min
}
this.value = this.digits === Number.POSITIVE_INFINITY ? val : val.toFixed(this.digits)
this.dispatchEvent(new Event('change'))
}
}
/** @param {KeyboardEvent} e */
#onArrowUp(e) {
if (['ArrowUp', 'ArrowDown'].includes(e.key) && this.#arrowTid) {
clearTimeout(this.#arrowTid)
this.#arrowTid = null
}
}
set digits(val) {
this.setAttribute('digits', val)
}
get digits() {
return this.#getNumberAttr('digits', 0, 0)
}
set max(val) {
this.setAttribute('max', val)
}
get max() {
return this.#getNumberAttr('max', Number.POSITIVE_INFINITY)
}
set min(val) {
this.setAttribute('min', val)
}
get min() {
return this.#getNumberAttr('min', 0)
}
set allowZero(val) {
this.setAttribute('allow-zero', val)
}
get allowZero() {
return this.getAttribute('allow-zero')
}
attributeChangedCallback(name, oldValue, newValue) {
newValue && this.removeAttribute(name)
}
/**
* 内部更新
*/
#update() {
if (this.getAttribute('type')) {
this.removeAttribute('type')
}
}
/**
* 获取数值属性值
* @param {string} name 属性名
* @param {number | string} [defaultVal=0] 默认值
* @param {number} [digits=Number.POSITIVE_INFINITY] 保留到小数点后几位
* @returns {number} 数值属性值
*/
#getNumberAttr(name, defaultVal = 0, digits = Number.POSITIVE_INFINITY) {
let val = this.getAttribute(name)
if (val == null) return defaultVal
val = Number.parseFloat(val)
if (Number.isNaN(val)) return defaultVal
if (digits === 0) {
val = Math.round(val)
} else if (digits !== Number.POSITIVE_INFINITY) {
val = Number.parseFloat(val.toFixed(digits))
}
return val
}
}, { extends: 'input' })
}