diff --git a/.ua/backlog.json b/.ua/backlog.json index fefbf3a..13ba2aa 100644 --- a/.ua/backlog.json +++ b/.ua/backlog.json @@ -64,6 +64,14 @@ "acceptance": "packages/skin/ui 新增 menu.ts:触发器 + 自绘弹层,items 支持 disabled / danger / divider,点条目 / 点外部 / Esc 关闭,↑↓ 环形导航(禁用项跳过),Enter/Space 选中,Esc 收起后焦点回触发器;只消费 --ua-* 令牌并带 prefers-reduced-motion 兜底;导出 + manifest + 文档 + llms-full.txt + demo 同步;npm run check 全绿", "status": "done", "notes": "API menu({ label, items, variant, align, onSelect });MenuItem = { value, label, disabled?, danger?, divider? };弹层 ua-open 同时挂在根与弹层(根管箭头旋转、弹层管出入场),危险条目取 danger 色;键盘 ↑↓ 环形导航 + Esc 回焦触发器;demo 新增 sec-menu 双示例(含禁用/危险/分隔线与右对齐)" + }, + { + "id": "d02", + "title": "select / radioGroup 补 disabled,修复 select 箭头旋转失效", + "motivation": "c01/c02 已覆盖 button/checkbox/switch/输入类,选择类仍缺禁用;同时发现 select 的 .ua-select.ua-open 选择器永不命中(ua-open 只挂在弹层),展开时箭头不旋转", + "acceptance": "SelectProps / RadioGroupProps 新增可选 disabled(boolean | Signal);禁用时 select 触发器置灰且不展开弹层,radioGroup 整组置灰不可选;select 箭头在展开时旋转 180°;demo 加信号禁用与静态禁用示例;manifest/文档/llms-full.txt 同步;npm run check 全绿", + "status": "done", + "notes": "disabled 仍走 h() 透传(原生 button/input 拦截交互)零辅助代码;select 根节点补 class 函数加 ua-open,箭头旋转选择器命中;CSS 通用禁用态列表并入 .ua-select-trigger 与 .ua-radio,另加 :has() 让单选行整行弱化、禁用触发器 hover 不改边框色" } ] } diff --git a/.ua/index.json b/.ua/index.json index b885ddf..27a149a 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-23T08:45:45.013Z", + "generatedAt": "2026-10-23T08:46:56.936Z", "repoRoot": ".", "packages": [ { @@ -147,9 +147,9 @@ "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", - "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画", + "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用", "menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭", - "radioGroup(props):单选组,options + value 信号双向绑定", + "radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用", "card(props, ...children):卡片容器", "badge(props):徽标,variant = default | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger", @@ -409,6 +409,11 @@ "t": "string", "d": "未选中时触发器显示的占位文案" }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层" + }, { "n": "onChange", "t": "(value: string) => void", @@ -485,6 +490,11 @@ "t": "Signal", "d": "选中值信号,双向绑定" }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "整组禁用:全部选项与文案置灰且不可选" + }, { "n": "onChange", "t": "(value: string) => void", @@ -496,7 +506,8 @@ "steps": [ "创建字符串信号,初值设为默认选项的 value", "传入 options 与 value,组内自动互斥", - "读取 size() 获取当前选中值" + "读取 size() 获取当前选中值", + "需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定" ] }, { diff --git a/.ua/iterations.log b/.ua/iterations.log index a6f3fb6..9e19d73 100644 --- a/.ua/iterations.log +++ b/.ua/iterations.log @@ -6,3 +6,4 @@ {"time":"2026-10-23T16:08:17+08:00","id":"c01","result":"done","summary":"button/checkbox/switchButton 补 disabled(boolean|Signal),复用 h() 布尔/信号既有语义零辅助代码;CSS 通用禁用态 + demo 信号联动示例;check 全绿"} {"time":"2026-10-23T16:10:31+08:00","id":"c02","result":"done","summary":"textField/textarea 补 disabled(boolean|Signal),与 c01 同模式;禁用态 CSS 覆盖输入类;demo locked 信号联动;check 全绿"} {"time": "2026-10-23T16:55:00+08:00", "id": "d01", "result": "done", "summary": "新增下拉菜单 menu:触发器 + 自绘弹层,disabled/danger/divider 条目,↑↓ 环形导航 + Esc 回焦,reduced-motion 兜底;manifest/llms-full/demo 同步,check 全绿"} +{"time": "2026-10-23T17:02:00+08:00", "id": "d02", "result": "done", "summary": "select/radioGroup 补 disabled(布尔|信号),并修复 select 根节点缺 ua-open 导致箭头不旋转的既有缺陷;CSS :has() 行级弱化;demo/manifest/llms 同步,check 全绿"} diff --git a/apps/demo/main.ts b/apps/demo/main.ts index 9c006f7..d159591 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -120,6 +120,13 @@ const app = h( switchButton({ label: '锁定', checked: locked }), // 关掉开关即解锁上面的勾选框与输入框 textField({ placeholder: '锁定后不可输入…', disabled: locked }), ), + h( + 'div', + { class: 'row' }, + select({ options: cities.map((c) => ({ value: c, label: c })), value: city, placeholder: '信号禁用的下拉…', disabled: locked }), + radioGroup({ options: [{ value: 's', label: '小' }, { value: 'm', label: '中' }], value: size, disabled: locked }), + radioGroup({ options: [{ value: 'x', label: '静态禁用' }], value: size, disabled: true }), + ), ), section( 'sec-feedback', diff --git a/llms-full.txt b/llms-full.txt index 05c70ef..7cd3c7d 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-23T08:45:45.013Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-23T08:46:56.936Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -165,6 +165,7 @@ const night = signal(true); switchButton({ label: '夜间模式', checked: night - options(必填):OptionItem[] —— 选项列表,每项 { value, label } - value:Signal —— 选中值信号,双向绑定 - placeholder:string —— 未选中时触发器显示的占位文案 +- disabled:boolean | Signal —— 禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层 - onChange(回调):(value: string) => void —— 选中回调 示例: @@ -214,6 +215,7 @@ menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'de - options(必填):OptionItem[] —— 选项列表 - value:Signal —— 选中值信号,双向绑定 +- disabled:boolean | Signal —— 整组禁用:全部选项与文案置灰且不可选 - onChange(回调):(value: string) => void —— 变更回调 示例: @@ -227,6 +229,7 @@ const size = signal('m'); radioGroup({ options: [{ value: 's', label: '小' }, { 1. 创建字符串信号,初值设为默认选项的 value 2. 传入 options 与 value,组内自动互斥 3. 读取 size() 获取当前选中值 +4. 需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定 现场演示:apps/docs/demo.html#sec-form @@ -542,9 +545,9 @@ API 速览: - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 -- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画 +- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用 - menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭 -- radioGroup(props):单选组,options + value 信号双向绑定 +- radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用 - card(props, ...children):卡片容器 - badge(props):徽标,variant = default | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger diff --git a/llms.txt b/llms.txt index 01e4c4c..7b763e4 100644 --- a/llms.txt +++ b/llms.txt @@ -52,9 +52,9 @@ - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 - - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画 + - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用 - menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭 - - radioGroup(props):单选组,options + value 信号双向绑定 + - radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用 - card(props, ...children):卡片容器 - badge(props):徽标,variant = default | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger diff --git a/packages/skin/ui/src/radioGroup.ts b/packages/skin/ui/src/radioGroup.ts index 4e25adf..5cd8d5e 100644 --- a/packages/skin/ui/src/radioGroup.ts +++ b/packages/skin/ui/src/radioGroup.ts @@ -6,7 +6,12 @@ export function radioGroup(props: RadioGroupProps): HTMLElement { const groupName = `ua-radio-${Math.random().toString(36).slice(2, 8)}`; // 组内唯一 name const radios: HTMLInputElement[] = []; for (const opt of props.options) { - const input = h('input', { type: 'radio', name: groupName, class: 'ua-radio' }) as HTMLInputElement; + const input = h('input', { + type: 'radio', + name: groupName, + class: 'ua-radio', + disabled: props.disabled, // 布尔或信号禁用,h() 直接透传 + }) as HTMLInputElement; input.value = opt.value; radios.push(input); input.addEventListener('change', () => { diff --git a/packages/skin/ui/src/select.ts b/packages/skin/ui/src/select.ts index 0a41f2b..15e0ef7 100644 --- a/packages/skin/ui/src/select.ts +++ b/packages/skin/ui/src/select.ts @@ -24,6 +24,7 @@ export function select(props: SelectProps): HTMLElement { class: 'ua-select-trigger', 'aria-haspopup': 'listbox', 'aria-expanded': () => String(open()), + disabled: props.disabled, // 布尔或信号禁用,h() 直接透传(原生 button 拦截点击) onClick: () => open(!open()), }, h('span', { class: () => `ua-select-value${value() ? '' : ' is-empty'}` }, () => current() || props.placeholder || ''), @@ -46,7 +47,7 @@ export function select(props: SelectProps): HTMLElement { ); const pop = h('div', { class: () => `ua-select-pop${open() ? ' ua-open' : ''}`, role: 'listbox' }, ...options); - const root = h('div', { class: 'ua-select' }, trigger, pop); + const root = h('div', { class: () => `ua-select${open() ? ' ua-open' : ''}` }, trigger, pop); // 根带开合类,供箭头旋转 root.addEventListener('keydown', (e: KeyboardEvent) => { if (e.key === 'Escape') open(false); // Esc 关闭弹层 diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index 7df012f..56678e0 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -67,6 +67,7 @@ export interface SelectProps { options: OptionItem[]; // 选项列表 value?: Signal; // 选中值信号,双向绑定 placeholder?: string; // 未选中时的占位项文案 + disabled?: boolean | Signal; // 禁用态:布尔直接置位,传信号则动态同步 onChange?: (value: string) => void; // 变更回调 } @@ -74,6 +75,7 @@ export interface SelectProps { export interface RadioGroupProps { options: OptionItem[]; // 选项列表 value?: Signal; // 选中值信号,双向绑定 + disabled?: boolean | Signal; // 禁用态:整组置灰且不可选 onChange?: (value: string) => void; // 变更回调 } diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 6fea3f3..ec3a8fc 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -18,9 +18,9 @@ "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", - "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画", + "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用", "menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭", - "radioGroup(props):单选组,options + value 信号双向绑定", + "radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用", "card(props, ...children):卡片容器", "badge(props):徽标,variant = default | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger", @@ -280,6 +280,11 @@ "t": "string", "d": "未选中时触发器显示的占位文案" }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层" + }, { "n": "onChange", "t": "(value: string) => void", @@ -356,6 +361,11 @@ "t": "Signal", "d": "选中值信号,双向绑定" }, + { + "n": "disabled", + "t": "boolean | Signal", + "d": "整组禁用:全部选项与文案置灰且不可选" + }, { "n": "onChange", "t": "(value: string) => void", @@ -367,7 +377,8 @@ "steps": [ "创建字符串信号,初值设为默认选项的 value", "传入 options 与 value,组内自动互斥", - "读取 size() 获取当前选中值" + "读取 size() 获取当前选中值", + "需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定" ] }, { diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index 8e2771d..cfc6541 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -852,9 +852,20 @@ /* —— 通用禁用态:置灰 + 禁用光标 —— */ .ua-btn:disabled, .ua-checkbox:disabled, +.ua-radio:disabled, .ua-switch-input:disabled, .ua-input:disabled, -.ua-textarea:disabled { +.ua-textarea:disabled, +.ua-select-trigger:disabled { + opacity: 0.45; + cursor: not-allowed; +} +/* 禁用时悬停不改边框色,避免误以为可交互 */ +.ua-select-trigger:disabled:hover { + border-color: var(--ua-color-border); +} +/* 单选组禁用:整行文案同步弱化 */ +.ua-check-row:has(.ua-radio:disabled) { opacity: 0.45; cursor: not-allowed; }