diff --git a/.ua/backlog.json b/.ua/backlog.json index 1530c84..7affa56 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -1364,6 +1364,14 @@ "acceptance": "SwitchProps 新增 loading(布尔或信号):加载中锁定交互(disabled 同值语义)、滑块换成转圈(reduced-motion 静态化为实心环)、input 挂 aria-busy;与 disabled 同时生效且不双写竞争;文档参数表 / api 说明 / 手把手与 llms-full.txt 同步;npm run check 全绿", "notes": "自补:组件能力补齐;loading 接管时 disabled 不再走 h() 绑定,单一 effect 写入避免竞争", "status": "done" + }, + { + "id": "g126", + "title": "数字输入补 precision 小数位", + "motivation": "金额、比例、权重这类小数场景:step 0.1 步进会累积出 0.30000000000000004 这样的浮点尾差直接写进信号,显示位数也无法约束,调用方只能自己在 onChange 里 toFixed 再写回", + "acceptance": "InputNumberProps 新增 precision(缺省不处理):写回与显示都按 toFixed(p) 归一(写回量化 + 输入框定长显示),步进浮点尾差消除;min/max 夹取语义不变;文档参数表 / api 说明 / 手把手与 llms-full.txt 同步;npm run check 全绿", + "notes": "自补:组件能力补齐;量化收敛在 emit/sync 单点,不改夹取与连发逻辑", + "status": "open" } ] } diff --git a/.ua/index.json b/.ua/index.json index 3705929..b665394 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T06:44:14.161Z", + "generatedAt": "2026-10-26T06:49:03.720Z", "repoRoot": ".", "packages": [ { @@ -144,7 +144,7 @@ "exports": [ "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交)", "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮,maxCount 设原生上限并实时显示字数", - "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块", + "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取,precision 小数位归一;prefix/suffix 可选渲染为整组前后附加块", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限", "slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值", "checkbox(props):复选框,checked 信号双向绑定,indeterminate 半选态(读屏播报 mixed),disabled 支持布尔或信号禁用", @@ -429,6 +429,11 @@ "d": "步长;缺省 1", "df": "1" }, + { + "n": "precision", + "t": "number", + "d": "小数位数:写回与显示都按 toFixed(p) 归一(消除步进浮点尾差,step 0.1 也干净);缺省不处理" + }, { "n": "disabled", "t": "boolean | Signal", @@ -456,6 +461,7 @@ "用 signal(1) 创建数字信号并传入 value 完成双向绑定", "点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析", "非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用", + "金额、比例这类小数场景传 precision:值与显示都固定小数位,步进不再出现 0.30000000000000004", "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] }, diff --git a/llms-full.txt b/llms-full.txt index 06f2f2d..dc9e77a 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T06:44:14.161Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T06:49:03.720Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -137,6 +137,7 @@ const name = signal(''); const el = textField({ value: name, placeholder: '输 - min:number —— 最小值;缺省无下限 - max:number —— 最大值;缺省无上限 - step(默认 1):number —— 步长;缺省 1 +- precision:number —— 小数位数:写回与显示都按 toFixed(p) 归一(消除步进浮点尾差,step 0.1 也干净);缺省不处理 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - onChange(回调):(value: number) => void —— 值变化回调(已夹取到合法区间) - prefix:string | HTMLElement —— 前置附加块(textField/inputNumber 在输入框内侧前、textarea 在上方);缺省不渲染 @@ -153,7 +154,8 @@ const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onC 1. 用 signal(1) 创建数字信号并传入 value 完成双向绑定 2. 点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析 3. 非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用 -4. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载 +4. 金额、比例这类小数场景传 precision:值与显示都固定小数位,步进不再出现 0.30000000000000004 +5. 需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载 现场演示:apps/docs/demo.html#sec-inputnumber @@ -1670,7 +1672,7 @@ API 速览: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交) - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮,maxCount 设原生上限并实时显示字数 -- inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块 +- inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取,precision 小数位归一;prefix/suffix 可选渲染为整组前后附加块 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限 - slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值 - checkbox(props):复选框,checked 信号双向绑定,indeterminate 半选态(读屏播报 mixed),disabled 支持布尔或信号禁用 diff --git a/llms.txt b/llms.txt index e653c67..299eb3f 100644 --- a/llms.txt +++ b/llms.txt @@ -49,7 +49,7 @@ - API: - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交) - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮,maxCount 设原生上限并实时显示字数 - - inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块 + - inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取,precision 小数位归一;prefix/suffix 可选渲染为整组前后附加块 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限 - slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值 - checkbox(props):复选框,checked 信号双向绑定,indeterminate 半选态(读屏播报 mixed),disabled 支持布尔或信号禁用 diff --git a/packages/skin/ui/src/inputNumber.ts b/packages/skin/ui/src/inputNumber.ts index 9267685..adff4bc 100644 --- a/packages/skin/ui/src/inputNumber.ts +++ b/packages/skin/ui/src/inputNumber.ts @@ -3,15 +3,16 @@ import { button } from './button.js'; import { addonWrap } from './addons.js'; import type { InputNumberProps } from './types.js'; -/*** 数字输入:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,键盘 ↑↓ 调节,长按连发步进 ***/ +/*** 数字输入:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,precision 小数位归一,键盘 ↑↓ 调节,长按连发步进 ***/ export function inputNumber(props: InputNumberProps): HTMLElement { const min = props.min ?? -Infinity; // 无下限缺省 const max = props.max ?? Infinity; // 无上限缺省 const step = props.step ?? 1; // 步长缺省 1 const clamp = (v: number): number => Math.min(max, Math.max(min, v)); // 任何来源的值都夹回合法区间 - const emit = (v: number): void => { const n = clamp(v); props.value(n); if (props.onChange) props.onChange(n); }; // 写回信号并回调 + const quantize = (n: number): number => (props.precision === undefined ? n : Number(n.toFixed(props.precision))); // precision 归一(顺带消除步进浮点尾差) + const emit = (v: number): void => { const n = quantize(clamp(v)); props.value(n); if (props.onChange) props.onChange(n); }; // 写回信号并回调 const input = h('input', { class: 'ua-input ua-input-number-field', inputmode: 'decimal', disabled: props.disabled, 'aria-label': '数字输入' }) as HTMLInputElement; - const sync = (): void => { const text = String(props.value()); if (input.value !== text) input.value = text; }; // 外部变化写回输入框 + const sync = (): void => { const text = props.precision === undefined ? String(props.value()) : props.value().toFixed(props.precision); if (input.value !== text) input.value = text; }; // 外部变化写回输入框(precision 定长显示) effect(sync); input.addEventListener('change', () => { const n = parseFloat(input.value); if (Number.isNaN(n)) sync(); else emit(n); }); // 非法输入回滚当前值 input.addEventListener('keydown', (e: KeyboardEvent) => { diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 5775772..a913381 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -201,6 +201,7 @@ export interface InputNumberProps { min?: number; // 最小值;缺省无下限 max?: number; // 最大值;缺省无上限 step?: number; // 步长;缺省 1 + precision?: number; // 小数位数:写回与显示都按 toFixed(p) 归一(消除步进浮点尾差);缺省不处理 disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 onChange?: (value: number) => void; // 值变化回调(已夹取到合法区间) prefix?: string | HTMLElement; // −/+ 按钮前的附加块(如货币符);缺省不渲染 diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index e5c87e9..dc44116 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -15,7 +15,7 @@ "exports": [ "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交)", "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮,maxCount 设原生上限并实时显示字数", - "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块", + "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取,precision 小数位归一;prefix/suffix 可选渲染为整组前后附加块", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限", "slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值", "checkbox(props):复选框,checked 信号双向绑定,indeterminate 半选态(读屏播报 mixed),disabled 支持布尔或信号禁用", @@ -300,6 +300,11 @@ "d": "步长;缺省 1", "df": "1" }, + { + "n": "precision", + "t": "number", + "d": "小数位数:写回与显示都按 toFixed(p) 归一(消除步进浮点尾差,step 0.1 也干净);缺省不处理" + }, { "n": "disabled", "t": "boolean | Signal", @@ -327,6 +332,7 @@ "用 signal(1) 创建数字信号并传入 value 完成双向绑定", "点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析", "非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用", + "金额、比例这类小数场景传 precision:值与显示都固定小数位,步进不再出现 0.30000000000000004", "需要图标、货币符、单位等前后缀时传 prefix/suffix(字符串或 DOM),控件会包进带边框的附加组,聚焦光环由组统一承载" ] },