From d685f66527fc33b83ae149a8fbcbed9a5afaf85e Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 11:51:41 +0800 Subject: [PATCH] =?UTF-8?q?iter(g40):=20=E4=BF=AE=E8=A1=A8=E5=8D=95?= =?UTF-8?q?=E5=8F=8C=E6=8F=90=E4=BA=A4=E5=B9=B6=E6=94=AF=E6=8C=81=20Enter?= =?UTF-8?q?=20=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/backlog.json | 8 ++++++++ .ua/index.json | 15 +++++++++++---- llms-full.txt | 7 +++++-- llms.txt | 2 +- packages/skin/ui/src/button.ts | 2 +- packages/skin/ui/src/form.ts | 10 ++++++++-- packages/skin/ui/src/types.ts | 1 + packages/skin/ui/ua.manifest.json | 13 ++++++++++--- 8 files changed, 45 insertions(+), 13 deletions(-) diff --git a/.ua/backlog.json b/.ua/backlog.json index d7eb6b1..db0754f 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -676,6 +676,14 @@ "acceptance": "ui.css 顶部统一 [class^=ua-]:focus-visible 描边兜底(不引入硬编码色);README 增「可访问性约定」章节;npm run check 全绿", "status": "done", "notes": "自补:a11y;避免组件各自 outline:none 丢失焦点指示" + }, + { + "id": "g40", + "title": "修表单双提交:button 显式 type + Enter 提交", + "motivation": "button() 不写 type,放进
里默认是 submit:点提交会先跑 click 回调再触发原生 submit(onSubmit 触发两次),点「重置」还会顺带提交表单", + "acceptance": "ButtonProps 增 type(缺省 button);form 内提交按钮用 submit、重置用 button;单行输入框 Enter 等同提交且 textarea 不受影响;文档同步;npm run check 全绿", + "status": "open", + "notes": "自补:既有组件缺陷修复" } ] } diff --git a/.ua/index.json b/.ua/index.json index ec73d01..74be2bd 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T03:49:26.217Z", + "generatedAt": "2026-10-26T03:51:18.113Z", "repoRoot": ".", "packages": [ { @@ -142,7 +142,7 @@ "api": { "entry": "src/index.ts", "exports": [ - "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用", + "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交)", "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮", "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限", @@ -298,6 +298,11 @@ "d": "视觉强度;缺省 primary", "df": "primary" }, + { + "n": "type", + "t": "'button' | 'submit' | 'reset'", + "d": "原生类型;缺省 button,表单内不会意外提交" + }, { "n": "disabled", "t": "boolean | Signal", @@ -314,7 +319,8 @@ "steps": [ "确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger", "绑定 onClick 回调处理点击逻辑", - "需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染" + "需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染", + "放进 里需要提交时显式传 type: \"submit\";缺省 button 不会触发原生提交" ] }, { @@ -1539,7 +1545,8 @@ "规则语义:required 非空(trim)、min/max 长度、pattern 正则、validator 自定义(true 通过 / 字符串为错误)", "非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程)", "错误态覆盖包裹式控件:标红作用于控件根与内部原生控件(含自绘 select 的触发器),并同步 aria-invalid,读屏与视觉一致", - "触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验" + "触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验", + "单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type=\"submit\",不会与点击回调重复触发" ] }, { diff --git a/llms-full.txt b/llms-full.txt index 3643d7f..93031e7 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T03:49:26.217Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T03:51:18.113Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -80,6 +80,7 @@ image({ src: '/a.png', alt: '架构图', width: 160 }) - label(必填):string —— 按钮文案 - variant(默认 primary):'primary' | 'ghost' | 'danger' —— 视觉强度;缺省 primary +- type:'button' | 'submit' | 'reset' —— 原生类型;缺省 button,表单内不会意外提交 - disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步 - onClick(回调):() => void —— 点击回调 @@ -94,6 +95,7 @@ button({ label: '提交', variant: 'primary', onClick: () => console.log('clicke 1. 确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger 2. 绑定 onClick 回调处理点击逻辑 3. 需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染 +4. 放进 里需要提交时显式传 type: "submit";缺省 button 不会触发原生提交 现场演示:apps/docs/demo.html#sec-counter @@ -867,6 +869,7 @@ const name = signal(''); const f = form({ onSubmit: submit }, [formItem({ label: 6. 非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程) 7. 错误态覆盖包裹式控件:标红作用于控件根与内部原生控件(含自绘 select 的触发器),并同步 aria-invalid,读屏与视觉一致 8. 触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验 +9. 单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type="submit",不会与点击回调重复触发 现场演示:apps/docs/demo.html#sec-formlab @@ -1170,7 +1173,7 @@ result({ status: 'success', title: '提交成功', description: '工单 #2049 API 速览: -- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 +- button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交) - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮 - inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限 diff --git a/llms.txt b/llms.txt index 47d6491..b642e18 100644 --- a/llms.txt +++ b/llms.txt @@ -47,7 +47,7 @@ - 类型: skin-dom · 目录: packages/skin/ui - 描述: DOM 皮肤组件库:表单 / 反馈 / 浮层 / 导航 / 数据展示,构建在 @ui-arch/frame 与 @ui-arch/table-core 之上,样式只消费 --ua-* 令牌(组件全量见 api.exports 或刚体清单) - API: - - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用 + - button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交) - textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮 - inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限 diff --git a/packages/skin/ui/src/button.ts b/packages/skin/ui/src/button.ts index ccce1b4..3c31d34 100644 --- a/packages/skin/ui/src/button.ts +++ b/packages/skin/ui/src/button.ts @@ -5,7 +5,7 @@ import type { ButtonProps } from './types.js'; export function button(props: ButtonProps): HTMLElement { return h( 'button', - { class: `ua-btn ua-btn-${props.variant ?? 'primary'}`, disabled: props.disabled, onClick: props.onClick }, // false/undefined 移除属性,Signal 走响应式 + { class: `ua-btn ua-btn-${props.variant ?? 'primary'}`, type: props.type ?? 'button', disabled: props.disabled, onClick: props.onClick }, // 缺省 button:表单内不隐式提交 props.label, ); } diff --git a/packages/skin/ui/src/form.ts b/packages/skin/ui/src/form.ts index 4beebcb..afc1ce3 100644 --- a/packages/skin/ui/src/form.ts +++ b/packages/skin/ui/src/form.ts @@ -71,8 +71,8 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement { } props.onSubmit?.(); }; - const submitBtn = button({ label: props.submitText ?? '提交', onClick: submit }); - const resetBtn = props.resetText ? button({ label: props.resetText, variant: 'ghost', onClick: props.onReset }) : null; + const submitBtn = button({ label: props.submitText ?? '提交', type: 'submit' }); // 走原生 submit,避免 click + submit 双触发 + const resetBtn = props.resetText ? button({ label: props.resetText, variant: 'ghost', type: 'button', onClick: props.onReset }) : null; // 重置不得提交表单 const actions = h('div', { class: 'ua-form-actions' }, submitBtn, resetBtn); const el = h( 'form', @@ -84,5 +84,11 @@ export function form(props: FormProps, items: FormItemHandle[]): HTMLElement { e.preventDefault(); // 拦截原生提交,走校验流程 submit(); }); + el.addEventListener('keydown', (e) => { + if (e.key !== 'Enter' || e.target instanceof HTMLTextAreaElement) return; // 多行输入框允许换行 + if (!(e.target instanceof HTMLInputElement)) return; // 只接管单行输入框 + e.preventDefault(); // 拦下浏览器隐式提交,统一走校验 + submit(); + }); return el; } diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 0ff6ab4..39d2d00 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -22,6 +22,7 @@ export interface PopoverProps { export interface ButtonProps { label: string; // 按钮文案 variant?: 'primary' | 'ghost' | 'danger'; // 视觉强度;缺省 primary + type?: 'button' | 'submit' | 'reset'; // 原生类型;缺省 button(表单内不会意外触发提交) disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 onClick?: () => void; // 点击回调 } diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 0fb9e4d..6666242 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -13,7 +13,7 @@ "api": { "entry": "src/index.ts", "exports": [ - "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用", + "button(props):按钮,variant = primary | ghost | danger,disabled 支持布尔或信号禁用;type 缺省 button(表单内不隐式提交)", "textField(props):文本输入,value 信号双向绑定,disabled 支持布尔或信号,onInput 输入回调;prefix/suffix 可选渲染为输入框内侧附加块,clearable 开启后有值即显一键清空按钮", "inputNumber(props):数字输入,−/+ 按钮与键盘 ↑↓ 调节(长按连发步进),min/max/step 夹取;prefix/suffix 可选渲染为整组前后附加块", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号,rows 控行数;prefix/suffix 可选渲染为上下附加块,maxCount 显示实时字数并设原生上限", @@ -169,6 +169,11 @@ "d": "视觉强度;缺省 primary", "df": "primary" }, + { + "n": "type", + "t": "'button' | 'submit' | 'reset'", + "d": "原生类型;缺省 button,表单内不会意外提交" + }, { "n": "disabled", "t": "boolean | Signal", @@ -185,7 +190,8 @@ "steps": [ "确定语义强度:主操作用 primary(默认),次要操作用 ghost,危险操作用 danger", "绑定 onClick 回调处理点击逻辑", - "需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染" + "需要禁用态时自行在回调首行卫语句判断,或外层包条件渲染", + "放进 里需要提交时显式传 type: \"submit\";缺省 button 不会触发原生提交" ] }, { @@ -1410,7 +1416,8 @@ "规则语义:required 非空(trim)、min/max 长度、pattern 正则、validator 自定义(true 通过 / 字符串为错误)", "非必填字段为空时跳过其余规则;Enter 键触发表单原生 submit(已拦截走校验流程)", "错误态覆盖包裹式控件:标红作用于控件根与内部原生控件(含自绘 select 的触发器),并同步 aria-invalid,读屏与视觉一致", - "触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验" + "触发时机用 input / change / focusout(均会冒泡),因此 checkbox / select / 单选组这类包裹式控件同样能失焦校验", + "单行输入框内按 Enter 等同点击提交(textarea 内仍是换行);提交按钮为 type=\"submit\",不会与点击回调重复触发" ] }, {