NumberBox 数字框

数字框让用户输入与调整数字(数量、价格、税率、字号……)。样式与行为还原 WinUI 3(Windows App SDK)的 NumberBox:它本质上是一个 TextBox + 数学解析 + NaN 语义,因此本组件内嵌 FluereInput(与 WinUI 模板里内嵌 TextBox 一致),并把 WinUI 的取值口径、校验模式、增减按钮与内联表达式逐项落地。

取值遵循 WinUI 的 Value ↔ Text 双通道:值变化时用格式化文本回写输入框;失焦或回车时解析文本再更新值。清空输入框会得到 null(对应 WinUI 的 NaN),此时显示 placeholder。

与 docs/todo.md 原计划的差异:原本打算复用 reka-ui 的 NumberField,但它的 NumberFieldInput 与 WinUI 有几处硬冲突 —— Home / End 被映射成最小值 / 最大值(WinUI 交给 TextBox 移动光标)、非法输入在失焦时把值清空(WinUI 保留原值)、方向键按步长吸附(WinUI 直接相加)、滚轮方向相反、LargeChange 写死 10 步。这里保留「复用」的思路但改为自持实现:format / parse / expression / step 四个纯函数层 + use-number-box / use-spin-repeat 两个 hook,可单独单测。

基础用法

v-model 绑定数值,header 是 WinUI 的标题行(同时作为缺省的可访问名),description 渲染在控件下方并作为 aria-describedby。

数量单价支持小数:失焦或回车后按 step 的小数位格式化

数量:12 · 单价:199.9(清空输入框会得到 null,对应 WinUI 的 NaN)

基础用法

区间与步进

min / max 对应 Minimum / Maximum(缺省不设上下界,此时也不渲染 aria-valuemin / aria-valuemax);step 是 SmallChange(方向键 / 滚轮 / 增减按钮),largeStep 是 LargeChange(PageUp / PageDown)。与 WinUI 一致:不做步长吸附,只在越界时钳制(或回绕)。

数量(0–500)比例(0–1,步进 0.1)

方向键 / 滚轮按 step 走,PageUp / PageDown 按 largeStep 走;越界被钳制在区间内,与 WinUI 一致不做步长吸附。

区间与步进

传 wrap-enabled 对应 IsWrapEnabled:到端点后回绕到另一端,且两个按钮始终可用。

增减按钮

spinButtonPlacementMode 对应 SpinButtonPlacementMode:hidden(默认)/ inline(输入框右侧内联上 / 下按钮)/ compact(聚焦时浮出按钮面板)。inline 与输入框共用一个 TextBox 边框,按钮 32×24、外边距 4、圆角 4;按住按钮会立即步进一次,随后每 250ms 重复(WinUI RepeatButton 的 Delay / Interval 缺省值)。按钮不进入 Tab 序列(WinUI IsTabStop=False),但可用 Enter / Space 激活。

字号(按住按钮可连续步进)缩放

内联按钮与输入框共用一个 TextBox 边框:按钮 32×24、外边距 4、圆角 4,按下后每 250ms 重复一次(WinUI RepeatButton 的 Delay / Interval 缺省值)。

内联按钮
不透明度栅格列数(聚焦后浮出上 / 下按钮)

紧凑模式在输入框右侧显示上 / 下指示器,聚焦时于右侧浮出 36×36 的按钮面板(圆角 8 + 阴影深度 16),失焦即收起。

紧凑按钮

内联表达式

accepts-expression 对应 AcceptsExpression:失焦或回车时求值 + - * / ^ 与括号组成的表达式,优先级为 ^ > * / > + -。求值后原始表达式不会被保留(会替换为结果)。

计算值预算(可输入 200*3+500)税率失焦或回车时求值,按 ^ > * / > + − 的优先级与括号计算

计算值:0 · 预算:1000 · 税率:0.06

内联表达式

校验模式

validationMode 对应 ValidationMode:

  • invalidInputOverwritten(默认):失焦 / 回车时,非法输入被当前值的格式化文本覆盖,越界值钳制回区间;
  • disabled:保留用户输入与越界值,交给消费方自行校验。

Escape 始终丢弃输入、回到当前值;Enter 结算输入(并吃掉按键,避免触发隐式表单提交,与 WinUI 处理 Enter 的方式一致)。

默认:非法输入被覆盖试试输入 abc —— 失焦后回到 20;输入 150 —— 钳制到 100ValidationMode=Disabled:保留输入文本原样保留,值仍停在最后一次合法解析结果

严格模式:20 · 宽松模式:20

校验模式

格式化与区域设置

formatOptions(Intl.NumberFormatOptions)对应 NumberFormatter,locale 决定小数点 / 分组分隔符 / 负号 —— 显示与解析共用同一套符号。SSR 应用建议显式传 locale:缺省时用运行环境的默认区域设置,而服务端与浏览器的默认值可能不同(例如 de-DE 的小数点),首帧会出现水合不一致。

WinUI 的默认 DecimalFormatter 被设成 IntegerDigits(1) + FractionDigits(0),在多个 WinAppSDK 版本上会把显示值四舍五入到整数(microsoft/microsoft-ui-xaml#8780:输入 25.8 失焦后显示 26)。Web 侧缺省不丢小数(maximumFractionDigits: 20),否则 step = 0.1 这类用法在显示层直接不可用;需要 WinUI 的整数观感时传 :format-options="{ maximumFractionDigits: 0 }" 即可。显示前还会做一次 WinUI 的 10 位有效数字舍入,抹掉 0.1 + 0.2 = 0.30000000000000004 这类浮点噪声。

整数外观(maximumFractionDigits: 0)25.8 显示为 26 —— 与 WinUI 默认 DecimalFormatter 的 FractionDigits(0) 同观感固定两位小数de-DE:1.234,5小数点与分组分隔符跟随 locale,解析也认同一套符号
格式化与区域设置

禁用

禁用(含增减按钮)禁用(无值)

禁用时输入框落到 TextControl…Disabled 档,增减按钮移到 …ForegroundDisabled;标题与说明同时变灰。

禁用状态

表单提交

位于 <form> 内并传 name 时,会补一个隐藏的原生 <input type="hidden">,提交 name=value(无值时提交空串)。

金额
提交到的 amount:—
表单提交

API

属性(Props)类型默认说明
modelValuenumber | null—当前值(v-model);null 表示无值(WinUI 的 NaN)
defaultValuenumber | nullnull非受控初始值
min / maxnumber—(不设界)区间(WinUI Minimum / Maximum)
stepnumber1步长(WinUI SmallChange)
largeStepnumber10大步长(WinUI LargeChange,PageUp / PageDown)
disabledbooleanfalse是否禁用
headerstring—顶部标题(WinUI Header),同时是缺省可访问名
labelstring—可访问名称(优先于 header)
descriptionstring—控件下方说明(WinUI Description),并作为 aria-describedby
placeholderstring—占位符(WinUI PlaceholderText),仅无值时显示
namestring—表单提交名,配合隐藏原生 input
localestring—BCP 47 语言标记,决定格式化与解析符号
formatOptionsIntl.NumberFormatOptionsmaximumFractionDigits: 20数值格式(WinUI NumberFormatter)
spinButtonPlacementMode'hidden' | 'compact' | 'inline''hidden'增减按钮呈现方式(WinUI SpinButtonPlacementMode)
validationMode'invalidInputOverwritten' | 'disabled''invalidInputOverwritten'校验模式(WinUI ValidationMode)
wrapEnabledbooleanfalse到端点后回绕(WinUI IsWrapEnabled)
acceptsExpressionbooleanfalse是否解析内联表达式(WinUI AcceptsExpression)
increaseLabelstring'Increase'加号按钮可访问名(WinUI 由资源本地化)
decreaseLabelstring'Decrease'减号按钮可访问名
事件(Events)载荷说明
update:modelValuenumber | null值变化(v-model)
valueChanged{ oldValue: number | null, newValue: number | null }值变化(对应 WinUI ValueChanged)
插槽(Slots)说明
header顶部标题行(WinUI Header / HeaderTemplate)
description控件下方说明(WinUI Description)

键盘与指针口径对照 NumberBox.cpp:方向键按 SmallChange、PageUp / PageDown 按 LargeChange、Enter 结算、Escape 回滚、获得焦点即全选文本;滚轮仅在输入框持有焦点时响应,向上滚为增(MouseWheelDelta > 0)。WinUI 把 Home / End 留给 TextBox,本组件同样不把它们当作步进键。

无障碍:输入框带 role="spinbutton" 与 aria-valuenow / aria-valuemin / aria-valuemax(未设置上下界时后两者不渲染,对应 WinUI 只在 Minimum / Maximum 被改写时才把它们拼进 UIA name 的口径);增减按钮不进 Tab 序列但可键盘激活,并带可访问名。

两处 Web 侧近似:紧凑浮层用 colorNeutralBackground1 + shadow16 近似 WinUI 的 Acrylic 表面(Fluent 令牌集中没有材质令牌);内联按钮出现时输入框右侧额外让出 84px,避免文本压到按钮上(WinUI 允许文本压在按钮下方)。

WinUI 的 Text 属性不作为独立 prop 暴露:Web 侧输入框文本由 v-model 加格式化派生(Value → Text),需要自定义文本展示时用 formatOptions;WinUI「同时设置 Text 与 Value 时以 Value 为准」的初始化顺序因此不适用。

v0.3.0-alpha.2