Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | 5x 5x 15x 15x 15x 15x 15x 15x 15x 15x 45x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 15x 13x 13x 13x 13x 15x 30x 30x 15x | import React, { useState, useEffect, useRef } from "react"; import classNames from "classnames"; import { StyledProps, Combine } from "../_type"; import { ControlledProps, useDefaultValue } from "../form/controlled"; import { withStatics } from "../_util/with-statics"; import { getPrecision } from "../_util/get-precision"; import { useConfig } from "../_util/config-context"; export interface InputNumberProps extends Combine<StyledProps, ControlledProps<number>> { /** 最小值 */ min?: number; /** 最大值 */ max?: number; /** * 使用按钮增减时的步长 * @default 1 */ step?: number; /** 单位 */ unit?: string; /** 是否可用 */ disabled?: boolean; /** * 精度(保留小数位数) */ precision?: number; } const DISABLED_CLS = "is-disabled"; /** * 数字输入组件 */ export const InputNumber = withStatics( function InputNumber(props: InputNumberProps) { const { classPrefix } = useConfig(); const { style, unit, disabled, className } = props; const { minus, input, plus } = useInputNumberHooks(props); const precision = props.precision || getPrecision(props.step); return ( <span className={classNames(`${classPrefix}-inputnum`, className, { [DISABLED_CLS]: disabled, })} style={style} > <span className={classNames(`${classPrefix}-inputnum__minus`, { [DISABLED_CLS]: minus.disabled, })} onMouseDown={disabled ? () => null : minus.handleMouseDown} > - </span> <input className={`${classPrefix}-input`} value={ typeof input.value === "number" ? input.value.toFixed(precision) : input.value } onChange={input.handleChange} onBlur={input.handleBlur} onKeyDown={input.handleKeyDown} disabled={disabled} /> <span className={classNames(`${classPrefix}-inputnum__plus`, { [DISABLED_CLS]: plus.disabled, })} onMouseDown={disabled ? () => null : plus.handleMouseDown} > + </span> {unit && ( <div className={`${classPrefix}-form__help-text--inline`}>{unit}</div> )} </span> ); }, { defaultLabelAlign: "middle", } ); /** * InputNumber 状态管理 */ function useInputNumberHooks(props: InputNumberProps) { let { step } = props; const { min, max, value, onChange } = useDefaultValue(props, 0); const isValidNumber = (num: any) => typeof num === "number" && !Number.isNaN(num); step = isValidNumber(step) ? step : 1; const precision = props.precision || getPrecision(step); const hasMax = isValidNumber(max); const hasMinus = isValidNumber(min); const [inputValue, setInputValue] = useState<string>(null); const hasInputValue = inputValue !== null; const parse = (newValue: string): number => newValue.trim() === "" ? 0 : parseFloat(newValue); const getCurrentValue = () => { let currentValue = value; if (hasInputValue && /\d/.test(inputValue)) { currentValue = parse(inputValue); // 解析失败,退回当前值 if (Number.isNaN(currentValue)) { currentValue = value; } } return currentValue; }; const canMinus = (value: number) => !hasMinus || value > min; const canPlus = (value: number) => !hasMax || value < max; const commit = (_newValue: string | number, event: React.SyntheticEvent) => { let newValue = _newValue; // 从 input 回调过来的,是字符串 if (typeof newValue === "string") { newValue = parse(newValue); } // 不能小于最小值 if (hasMinus) { newValue = Math.max(min, newValue); } // 不能大于最大值 if (hasMax) { newValue = Math.min(max, newValue); } // 解析失败,还原旧值 if (Number.isNaN(newValue)) { newValue = value; } // 处理精度 newValue = parse(newValue.toFixed(precision)); if (typeof onChange === "function") { onChange(newValue, { event }); } else { console.warn("InputNumber 是受控组件,请传入 onChange 属性处理值变化"); } }; // value 发生变更,清空暂存的值 useEffect(() => setInputValue(null), [value]); // 长按增减按钮会持续操作,鼠标抬起,或者组件销毁时要清空 const autoStepTimer = useRef<any>(0); const clear = () => clearTimeout(autoStepTimer.current); useEffect(() => { window.addEventListener("mouseup", clear, true); // destroy return () => { clear(); window.removeEventListener("mouseup", clear); }; }, []); // 为按钮提供步长调整逻辑 const stepper = (step: number) => { // 步长的符号影响是否允许进行下一步长的判断方法 const canStep = step > 0 ? canPlus : canMinus; return { disabled: !canStep(value), // 点击按钮的时候,先变更一次。然后,只要鼠标不抬起,自动连续变更 handleMouseDown: (evt: React.MouseEvent) => { let currentValue = getCurrentValue(); const performStep = () => { if (canStep(currentValue)) { commit((currentValue += step), evt); } }; performStep(); clear(); // 1 秒后,开始自动递增 autoStepTimer.current = setTimeout(() => { const autoPerform = () => { performStep(); autoStepTimer.current = setTimeout(autoPerform, 50); }; autoPerform(); }, 700); // 保留事件内容,否则在自动步长执行的时候,事件不可用 evt.persist(); // 阻止事件变成 blur 事件,导致二次设置 evt.preventDefault(); }, }; }; return { minus: stepper(-step), plus: stepper(step), input: { value: hasInputValue ? inputValue : value, handleChange: (evt: React.ChangeEvent<HTMLInputElement>) => { setInputValue(evt.target.value); }, handleBlur: (evt: React.FocusEvent<HTMLInputElement>) => { commit(evt.target.value, evt); setInputValue(null); }, handleKeyDown: (evt: React.KeyboardEvent<HTMLInputElement>) => { const currentValue = getCurrentValue(); if (canPlus(currentValue) && evt.keyCode === 38) { // ArrowUp evt.preventDefault(); commit(currentValue + step, evt); } else if (canMinus(currentValue) && evt.keyCode === 40) { // ArrowDown evt.preventDefault(); commit(currentValue - step, evt); } // Enter if (evt.keyCode === 13) { commit(currentValue, evt); setInputValue(null); } }, }, }; } |