Compare commits
3 Commits
b89d1cd801
...
47bea0b69c
| Author | SHA1 | Date | |
|---|---|---|---|
| 47bea0b69c | |||
| 20d7b9440f | |||
| d7226f452d |
@@ -276,6 +276,14 @@
|
|||||||
"id": "d29",
|
"id": "d29",
|
||||||
"title": "menu 补 Home/End 跳转,与 select 键盘导航对称",
|
"title": "menu 补 Home/End 跳转,与 select 键盘导航对称",
|
||||||
"status": "done"
|
"status": "done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e01",
|
||||||
|
"title": "新增 inputNumber 数字输入组件",
|
||||||
|
"motivation": "数量、份数等离散数值输入是表单常见场景,纯 textField 缺少步进语义;加减按钮 + 键盘调节可复用既有 button 与信号双向绑定模式",
|
||||||
|
"acceptance": "packages/skin/ui 新增 inputNumber.ts:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,键盘 ↑↓ 调节,非法输入回滚,disabled 支持布尔或信号;只消费 --ua-* 令牌;导出、manifest、文档、llms-full.txt 同步;demo 加一节演示;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "inputNumber(props):−/+ 复用 ghost button(自带微动画与 disabled 透传),输入框 inputmode=decimal,change 时 parseFloat 解析、非法回滚当前值;↑↓ 步进并 preventDefault;clamp 统一夹取 min/max(缺省 ±Infinity);aria-label 三件套 + role=group;CSS 步进按钮收窄、tabular-nums;demo 新增 sec-inputnumber(与禁用态实例共用 qty 信号);manifest/docs/llms-full/README 同步为 29 组件"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+48
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-23T09:12:34.151Z",
|
"generatedAt": "2026-10-23T09:49:54.480Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -144,6 +144,7 @@
|
|||||||
"exports": [
|
"exports": [
|
||||||
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
||||||
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
|
"inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取",
|
||||||
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
||||||
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
@@ -297,6 +298,52 @@
|
|||||||
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`"
|
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "inputNumber",
|
||||||
|
"title": "数字输入 InputNumber",
|
||||||
|
"demo": "sec-inputnumber",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"n": "value",
|
||||||
|
"t": "Signal<number>",
|
||||||
|
"r": 1,
|
||||||
|
"d": "当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "min",
|
||||||
|
"t": "number",
|
||||||
|
"d": "最小值;缺省无下限"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "max",
|
||||||
|
"t": "number",
|
||||||
|
"d": "最大值;缺省无上限"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "step",
|
||||||
|
"t": "number",
|
||||||
|
"d": "步长;缺省 1",
|
||||||
|
"df": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "disabled",
|
||||||
|
"t": "boolean | Signal<boolean>",
|
||||||
|
"d": "禁用态:布尔直接置位,传信号则动态同步"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onChange",
|
||||||
|
"t": "(value: number) => void",
|
||||||
|
"k": 1,
|
||||||
|
"d": "值变化回调(已夹取到合法区间)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
|
||||||
|
"steps": [
|
||||||
|
"用 signal(1) 创建数字信号并传入 value 完成双向绑定",
|
||||||
|
"点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析",
|
||||||
|
"非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "textarea",
|
"id": "textarea",
|
||||||
"title": "多行输入 Textarea",
|
"title": "多行输入 Textarea",
|
||||||
|
|||||||
@@ -34,3 +34,4 @@
|
|||||||
{"ts": "2026-10-23T09:11:42+00:00", "round": 27, "id": "d27", "result": "done", "commit": "fac74a3", "summary": "modal 焦点陷阱 + dialog 语义"}
|
{"ts": "2026-10-23T09:11:42+00:00", "round": 27, "id": "d27", "result": "done", "commit": "fac74a3", "summary": "modal 焦点陷阱 + dialog 语义"}
|
||||||
{"ts": "2026-10-23T09:11:42+00:00", "round": 28, "id": "d28", "result": "done", "commit": "", "summary": "drawer 焦点陷阱对齐 modal"}
|
{"ts": "2026-10-23T09:11:42+00:00", "round": 28, "id": "d28", "result": "done", "commit": "", "summary": "drawer 焦点陷阱对齐 modal"}
|
||||||
{"ts": "2026-10-23T09:12:49+00:00", "round": 29, "id": "d29", "result": "done", "commit": "", "summary": "menu Home/End 跳转对齐 select"}
|
{"ts": "2026-10-23T09:12:49+00:00", "round": 29, "id": "d29", "result": "done", "commit": "", "summary": "menu Home/End 跳转对齐 select"}
|
||||||
|
{"ts":"2026-10-23T17:50:14+08:00","id":"e01","result":"done","commit":"20d7b94","summary":"新增 inputNumber 数字输入:−/+ 按钮 + 键盘上下步进,信号双向绑定,min/max/step 夹取,门禁全绿"}
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@
|
|||||||
调度器(每小时唤起)**不是**迭代器,只做两件事:健康检查 + 中断重启。真正的迭代是单次唤起内的**连续循环**——反复执行《每轮迭代流程》,直到 backlog 清空或时间预算耗尽,中间不停歇。
|
调度器(每小时唤起)**不是**迭代器,只做两件事:健康检查 + 中断重启。真正的迭代是单次唤起内的**连续循环**——反复执行《每轮迭代流程》,直到 backlog 清空或时间预算耗尽,中间不停歇。
|
||||||
|
|
||||||
- **并发锁**:`.ua/loop.lock`(含时间戳,不入库)。唤起时若锁存在且心跳在 15 分钟内 → 已有实例在跑,直接退出;否则写锁开始循环。每轮开始前刷新锁内时间戳作心跳。
|
- **并发锁**:`.ua/loop.lock`(含时间戳,不入库)。唤起时若锁存在且心跳在 15 分钟内 → 已有实例在跑,直接退出;否则写锁开始循环。每轮开始前刷新锁内时间戳作心跳。
|
||||||
- **时间预算**:单次唤起最多连续迭代约 50 分钟,到点在完成当前轮后停止(防止与下次唤起重叠)。
|
- **时间预算**:单次唤起最多连续迭代约 50 分钟,到点在完成当前轮后停止(防止与下次唤起重叠)。**预算判断必须基于客观时钟**:点火时用 `date +%s` 记录起点并写入 `.ua/loop.lock`,每轮开始时重新取 `date +%s` 与起点求差,差值 ≥ 2700 秒(45 分钟)即到点;**禁止凭感觉估算时长**——LLM 无内部时钟,自估时长系统性偏高(实测自报 40+ 分钟而实际约 30 分钟)。
|
||||||
- **熔断**:同一 backlog 条目连续 2 轮失败 → 停止循环,锁照常释放,等待下次唤起。
|
- **熔断**:同一 backlog 条目连续 2 轮失败 → 停止循环,锁照常释放,等待下次唤起。
|
||||||
- **重启语义**:锁心跳超过 15 分钟视为循环中断(崩溃/超时),调度器删旧锁、重新点火。
|
- **重启语义**:锁心跳超过 15 分钟视为循环中断(崩溃/超时),调度器删旧锁、重新点火。
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ qa/probe ──门禁通过──▶ qa/stable ──人工审阅──▶ main
|
|||||||
4. **门禁**:跑 `npm run check`(build → style → typecheck → smoke → index)。
|
4. **门禁**:跑 `npm run check`(build → style → typecheck → smoke → index)。
|
||||||
- **通过**:提交到 `qa/probe`(commit message 以 `iter:` 开头,注明 backlog 条目 id),推送 `qa/probe`,fast-forward 合并到 `qa/stable` 并推送;将 backlog 条目 `status` 置为 `done` 并随迭代提交。
|
- **通过**:提交到 `qa/probe`(commit message 以 `iter:` 开头,注明 backlog 条目 id),推送 `qa/probe`,fast-forward 合并到 `qa/stable` 并推送;将 backlog 条目 `status` 置为 `done` 并随迭代提交。
|
||||||
- **失败**:尝试修复一次;仍失败则放弃本轮——`qa/probe` 直接重置回 `qa/stable`,backlog 条目在 `notes` 里追加失败原因、`status` 保持 `open`,提交并推送该元数据变更(仅改 `.ua/backlog.json`,不碰代码)。
|
- **失败**:尝试修复一次;仍失败则放弃本轮——`qa/probe` 直接重置回 `qa/stable`,backlog 条目在 `notes` 里追加失败原因、`status` 保持 `open`,提交并推送该元数据变更(仅改 `.ua/backlog.json`,不碰代码)。
|
||||||
5. **留痕**:在 `.ua/iterations.log` 追加一行 JSON:`{时间, 条目id, 结果, 摘要}`,随本轮提交。
|
5. **留痕**:在 `.ua/iterations.log` 追加一行 JSON,**字段统一**:`{"ts":"本地ISO时间","id":"条目id","result":"done|aborted","commit":"短哈希","summary":"一句话"}`。硬性规范:`ts` 必须来自 `date` 命令实测(本地时间,带 +08:00 偏移,**禁止手写或用 UTC 换算**);时长结论必须来自两次时间查询之差,禁止使用「大约/接近」等估算词;并发实例格式不得自创变体。随本轮提交。
|
||||||
6. **边界**:不改 `main` 分支保护、不删分支、不动 `.workbuddy/`、不重写历史、不 force push 到 `qa/stable` 与 `main`。
|
6. **边界**:不改 `main` 分支保护、不删分支、不动 `.workbuddy/`、不重写历史、不 force push 到 `qa/stable` 与 `main`。
|
||||||
|
|
||||||
## 选题规则(自主补点子时)
|
## 选题规则(自主补点子时)
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ npm run demo # 打开演示:apps/demo/dist/demo.html
|
|||||||
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
|
npm run docs # 生成人类文档站 apps/docs/index.html + Agent 全量文档 llms-full.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
## 组件(共 28 个)
|
## 组件(共 29 个)
|
||||||
|
|
||||||
<!-- ua:components -->
|
<!-- ua:components -->
|
||||||
accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · drawer · form · menu · modal · pagination · popover · progress · radioGroup · select · skeleton · slider · spinner · switchButton · tabs · tag · textField · textarea · toast · tooltip
|
accordion · alert · avatar · badge · breadcrumb · button · card · checkbox · dataTable · drawer · form · inputNumber · menu · modal · pagination · popover · progress · radioGroup · select · skeleton · slider · spinner · switchButton · tabs · tag · textField · textarea · toast · tooltip
|
||||||
<!-- /ua:components -->
|
<!-- /ua:components -->
|
||||||
|
|
||||||
分组:表单(button / textField / textarea / checkbox / switchButton / select / radioGroup / slider / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion)、数据(dataTable / tag / avatar / card)。
|
分组:表单(button / textField / inputNumber / textarea / checkbox / switchButton / select / radioGroup / slider / form / formItem)、反馈(alert / toast / spinner / progress / skeleton / badge)、浮层(tooltip / popover / modal / drawer / menu)、导航(tabs / breadcrumb / pagination / accordion)、数据(dataTable / tag / avatar / card)。
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ accordion · alert · avatar · badge · breadcrumb · button · card · checkbo
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) |
|
| `packages/core/frame` | @ui-arch/frame:signal / computed / effect + hyperscript(无虚拟 DOM) |
|
||||||
| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
|
| `packages/core/table-core` | @ui-arch/table-core:headless 表格(filter → sort → paginate 派生管线) |
|
||||||
| `packages/skin/ui` | @ui-arch/ui:28 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
| `packages/skin/ui` | @ui-arch/ui:29 个 DOM 组件 + ui.css(只消费 `--ua-*` 令牌) |
|
||||||
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
|
| `packages/design/tokens` | @ui-arch/tokens:设计令牌(暗色只换值不换名) |
|
||||||
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
|
| `packages/tools/cli` | @ui-arch/cli:`ua` 命令行与 stdio MCP server |
|
||||||
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
|
| `apps/demo` | 交互演示(esbuild 打包成单文件) |
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
drawer,
|
drawer,
|
||||||
form,
|
form,
|
||||||
formItem,
|
formItem,
|
||||||
|
inputNumber,
|
||||||
menu,
|
menu,
|
||||||
modal,
|
modal,
|
||||||
pagination,
|
pagination,
|
||||||
@@ -111,6 +112,9 @@ const tags = signal(['设计', '前端', '组件库']);
|
|||||||
// —— 滑块:音量与禁用态演示 ——
|
// —— 滑块:音量与禁用态演示 ——
|
||||||
const volume = signal(40);
|
const volume = signal(40);
|
||||||
|
|
||||||
|
// —— 数字输入:购买数量演示,与禁用态实例共用同一信号 ——
|
||||||
|
const qty = signal(1);
|
||||||
|
|
||||||
// —— 受控选中:dataTable 内部勾选实时写回,外部可清空 ——
|
// —— 受控选中:dataTable 内部勾选实时写回,外部可清空 ——
|
||||||
const picked = signal<RowKey[]>([]);
|
const picked = signal<RowKey[]>([]);
|
||||||
|
|
||||||
@@ -232,6 +236,17 @@ const content = h(
|
|||||||
slider({ value: volume, disabled: locked }),
|
slider({ value: volume, disabled: locked }),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
section(
|
||||||
|
'sec-inputnumber',
|
||||||
|
'数字输入',
|
||||||
|
'inputNumber:−/+ 按钮与键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取。',
|
||||||
|
h(
|
||||||
|
'div',
|
||||||
|
{ class: 'row' },
|
||||||
|
inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => toast.info(`数量 ${v}`) }),
|
||||||
|
inputNumber({ value: qty, disabled: locked }),
|
||||||
|
),
|
||||||
|
),
|
||||||
section(
|
section(
|
||||||
'sec-feedback',
|
'sec-feedback',
|
||||||
'反馈与提示',
|
'反馈与提示',
|
||||||
|
|||||||
+27
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-23T09:06:10.925Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-23T09:49:54.480Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -96,6 +96,31 @@ const name = signal(''); const el = textField({ value: name, placeholder: '输
|
|||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-bind
|
现场演示:apps/docs/demo.html#sec-bind
|
||||||
|
|
||||||
|
### inputNumber — 数字输入 InputNumber
|
||||||
|
|
||||||
|
参数:
|
||||||
|
|
||||||
|
- value(必填):Signal<number> —— 当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)
|
||||||
|
- min:number —— 最小值;缺省无下限
|
||||||
|
- max:number —— 最大值;缺省无上限
|
||||||
|
- step(默认 1):number —— 步长;缺省 1
|
||||||
|
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||||||
|
- onChange(回调):(value: number) => void —— 值变化回调(已夹取到合法区间)
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })
|
||||||
|
```
|
||||||
|
|
||||||
|
手把手:
|
||||||
|
|
||||||
|
1. 用 signal(1) 创建数字信号并传入 value 完成双向绑定
|
||||||
|
2. 点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析
|
||||||
|
3. 非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用
|
||||||
|
|
||||||
|
现场演示:apps/docs/demo.html#sec-inputnumber
|
||||||
|
|
||||||
### textarea — 多行输入 Textarea
|
### textarea — 多行输入 Textarea
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
@@ -755,6 +780,7 @@ API 速览:
|
|||||||
|
|
||||||
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
||||||
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
|
- inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取
|
||||||
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
||||||
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
- API:
|
- API:
|
||||||
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用
|
||||||
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
|
- inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取
|
||||||
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
- slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值
|
||||||
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './types.js';
|
export * from './types.js';
|
||||||
export { button } from './button.js';
|
export { button } from './button.js';
|
||||||
export { textField } from './textField.js';
|
export { textField } from './textField.js';
|
||||||
|
export { inputNumber } from './inputNumber.js';
|
||||||
export { textarea } from './textarea.js';
|
export { textarea } from './textarea.js';
|
||||||
export { slider } from './slider.js';
|
export { slider } from './slider.js';
|
||||||
export { checkbox } from './checkbox.js';
|
export { checkbox } from './checkbox.js';
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { effect, h } from '@ui-arch/frame';
|
||||||
|
import { button } from './button.js';
|
||||||
|
import type { InputNumberProps } from './types.js';
|
||||||
|
|
||||||
|
/*** 数字输入:−/+ 按钮 + 输入框三段式,value 信号双向绑定,min/max/step 超界自动夹取,键盘 ↑↓ 调节 ***/
|
||||||
|
export function inputNumber(props: InputNumberProps): HTMLDivElement {
|
||||||
|
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 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; }; // 外部变化写回输入框
|
||||||
|
effect(sync);
|
||||||
|
input.addEventListener('change', () => { const n = parseFloat(input.value); if (Number.isNaN(n)) sync(); else emit(n); }); // 非法输入回滚当前值
|
||||||
|
input.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
|
if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return; // 卫语句:只拦截上下方向键
|
||||||
|
e.preventDefault();
|
||||||
|
emit(props.value() + (e.key === 'ArrowUp' ? step : -step)); // ↑ 加步长 / ↓ 减步长
|
||||||
|
});
|
||||||
|
const dec = button({ label: '−', variant: 'ghost', disabled: props.disabled, onClick: () => emit(props.value() - step) });
|
||||||
|
const inc = button({ label: '+', variant: 'ghost', disabled: props.disabled, onClick: () => emit(props.value() + step) });
|
||||||
|
dec.setAttribute('aria-label', '减少');
|
||||||
|
inc.setAttribute('aria-label', '增加');
|
||||||
|
return h('div', { class: 'ua-input-number', role: 'group' }, dec, input, inc) as HTMLDivElement;
|
||||||
|
}
|
||||||
@@ -165,6 +165,16 @@ export interface TextareaProps {
|
|||||||
onInput?: (value: string) => void; // 输入回调
|
onInput?: (value: string) => void; // 输入回调
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 数字输入入参 ***/
|
||||||
|
export interface InputNumberProps {
|
||||||
|
value: Signal<number>; // 当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)
|
||||||
|
min?: number; // 最小值;缺省无下限
|
||||||
|
max?: number; // 最大值;缺省无上限
|
||||||
|
step?: number; // 步长;缺省 1
|
||||||
|
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
||||||
|
onChange?: (value: number) => void; // 值变化回调(已夹取到合法区间)
|
||||||
|
}
|
||||||
|
|
||||||
/*** 徽标入参 ***/
|
/*** 徽标入参 ***/
|
||||||
export interface BadgeProps {
|
export interface BadgeProps {
|
||||||
label: string; // 徽标文案
|
label: string; // 徽标文案
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"exports": [
|
"exports": [
|
||||||
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
"button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用",
|
||||||
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
|
"inputNumber(props):数字输入,−/+ 按钮 + 键盘 ↑↓ 调节,value 信号双向绑定,min/max/step 超界自动夹取",
|
||||||
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
"slider(props):滑块,value 信号双向绑定,min/max/step 可配,原生 range 承载键盘交互,右侧可显示当前值",
|
||||||
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
|
||||||
@@ -168,6 +169,52 @@
|
|||||||
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`"
|
"在任意响应式位置用 name() 读取最新值,如 () => `你好,${name()}`"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "inputNumber",
|
||||||
|
"title": "数字输入 InputNumber",
|
||||||
|
"demo": "sec-inputnumber",
|
||||||
|
"props": [
|
||||||
|
{
|
||||||
|
"n": "value",
|
||||||
|
"t": "Signal<number>",
|
||||||
|
"r": 1,
|
||||||
|
"d": "当前值信号,双向绑定(按钮/键盘写回、外部赋值同步)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "min",
|
||||||
|
"t": "number",
|
||||||
|
"d": "最小值;缺省无下限"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "max",
|
||||||
|
"t": "number",
|
||||||
|
"d": "最大值;缺省无上限"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "step",
|
||||||
|
"t": "number",
|
||||||
|
"d": "步长;缺省 1",
|
||||||
|
"df": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "disabled",
|
||||||
|
"t": "boolean | Signal<boolean>",
|
||||||
|
"d": "禁用态:布尔直接置位,传信号则动态同步"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onChange",
|
||||||
|
"t": "(value: number) => void",
|
||||||
|
"k": 1,
|
||||||
|
"d": "值变化回调(已夹取到合法区间)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"example": "const qty = signal(1); const el = inputNumber({ value: qty, min: 1, max: 99, onChange: (v) => console.log(v) })",
|
||||||
|
"steps": [
|
||||||
|
"用 signal(1) 创建数字信号并传入 value 完成双向绑定",
|
||||||
|
"点击 −/+ 按钮或在输入框聚焦时按 ↑↓ 以 step 调节;直接输入在失焦或回车时解析",
|
||||||
|
"非法输入自动回滚为当前值;min/max 超界自动夹取,disabled 支持布尔或信号禁用"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "textarea",
|
"id": "textarea",
|
||||||
"title": "多行输入 Textarea",
|
"title": "多行输入 Textarea",
|
||||||
|
|||||||
@@ -687,6 +687,22 @@
|
|||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* —— 数字输入 —— */
|
||||||
|
.ua-input-number {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.ua-input-number .ua-btn {
|
||||||
|
padding: 5px 12px; /* 收窄为步进按钮 */
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.ua-input-number-field {
|
||||||
|
width: 76px;
|
||||||
|
text-align: center;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
/* —— 徽标 —— */
|
/* —— 徽标 —— */
|
||||||
.ua-badge {
|
.ua-badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
Reference in New Issue
Block a user