iter: d02 select/radioGroup 补 disabled + 修复 select 箭头旋转

This commit is contained in:
ui-arch-bot
2026-10-23 16:47:03 +08:00
parent 84277a744e
commit 6b9fa95d1e
11 changed files with 75 additions and 15 deletions
+8
View File
@@ -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 全绿", "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", "status": "done",
"notes": "API menu({ label, items, variant, align, onSelect });MenuItem = { value, label, disabled?, danger?, divider? };弹层 ua-open 同时挂在根与弹层(根管箭头旋转、弹层管出入场),危险条目取 danger 色;键盘 ↑↓ 环形导航 + Esc 回焦触发器;demo 新增 sec-menu 双示例(含禁用/危险/分隔线与右对齐)" "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<boolean>);禁用时 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 不改边框色"
} }
] ]
} }
+15 -4
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "ui-arch-registry-index/0.1", "$schema": "ui-arch-registry-index/0.1",
"generatedAt": "2026-10-23T08:45:45.013Z", "generatedAt": "2026-10-23T08:46:56.936Z",
"repoRoot": ".", "repoRoot": ".",
"packages": [ "packages": [
{ {
@@ -147,9 +147,9 @@
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画", "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用",
"menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭", "menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭",
"radioGroup(props):单选组,options + value 信号双向绑定", "radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用",
"card(props, ...children):卡片容器", "card(props, ...children):卡片容器",
"badge(props):徽标,variant = default | success | warning | danger", "badge(props):徽标,variant = default | success | warning | danger",
"alert(props):提示条,variant = info | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger",
@@ -409,6 +409,11 @@
"t": "string", "t": "string",
"d": "未选中时触发器显示的占位文案" "d": "未选中时触发器显示的占位文案"
}, },
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层"
},
{ {
"n": "onChange", "n": "onChange",
"t": "(value: string) => void", "t": "(value: string) => void",
@@ -485,6 +490,11 @@
"t": "Signal<string>", "t": "Signal<string>",
"d": "选中值信号,双向绑定" "d": "选中值信号,双向绑定"
}, },
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "整组禁用:全部选项与文案置灰且不可选"
},
{ {
"n": "onChange", "n": "onChange",
"t": "(value: string) => void", "t": "(value: string) => void",
@@ -496,7 +506,8 @@
"steps": [ "steps": [
"创建字符串信号,初值设为默认选项的 value", "创建字符串信号,初值设为默认选项的 value",
"传入 options 与 value,组内自动互斥", "传入 options 与 value,组内自动互斥",
"读取 size() 获取当前选中值" "读取 size() 获取当前选中值",
"需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定"
] ]
}, },
{ {
+1
View File
@@ -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: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: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-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 全绿"}
+7
View File
@@ -120,6 +120,13 @@ const app = h(
switchButton({ label: '锁定', checked: locked }), // 关掉开关即解锁上面的勾选框与输入框 switchButton({ label: '锁定', checked: locked }), // 关掉开关即解锁上面的勾选框与输入框
textField({ placeholder: '锁定后不可输入…', disabled: 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( section(
'sec-feedback', 'sec-feedback',
+6 -3
View File
@@ -1,6 +1,6 @@
# @ui-arch 组件库 Agent 文档(无图形版) # @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)。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
## 快速上手 ## 快速上手
@@ -165,6 +165,7 @@ const night = signal(true); switchButton({ label: '夜间模式', checked: night
- options(必填):OptionItem[] —— 选项列表,每项 { value, label } - options(必填):OptionItem[] —— 选项列表,每项 { value, label }
- value:Signal<string> —— 选中值信号,双向绑定 - value:Signal<string> —— 选中值信号,双向绑定
- placeholder:string —— 未选中时触发器显示的占位文案 - placeholder:string —— 未选中时触发器显示的占位文案
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层
- onChange(回调):(value: string) => void —— 选中回调 - onChange(回调):(value: string) => void —— 选中回调
示例: 示例:
@@ -214,6 +215,7 @@ menu({ label: '操作', items: [{ value: 'edit', label: '编辑' }, { value: 'de
- options(必填):OptionItem[] —— 选项列表 - options(必填):OptionItem[] —— 选项列表
- value:Signal<string> —— 选中值信号,双向绑定 - value:Signal<string> —— 选中值信号,双向绑定
- disabled:boolean | Signal<boolean> —— 整组禁用:全部选项与文案置灰且不可选
- onChange(回调):(value: string) => void —— 变更回调 - onChange(回调):(value: string) => void —— 变更回调
示例: 示例:
@@ -227,6 +229,7 @@ const size = signal('m'); radioGroup({ options: [{ value: 's', label: '小' }, {
1. 创建字符串信号,初值设为默认选项的 value 1. 创建字符串信号,初值设为默认选项的 value
2. 传入 options 与 value,组内自动互斥 2. 传入 options 与 value,组内自动互斥
3. 读取 size() 获取当前选中值 3. 读取 size() 获取当前选中值
4. 需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定
现场演示:apps/docs/demo.html#sec-form 现场演示:apps/docs/demo.html#sec-form
@@ -542,9 +545,9 @@ API 速览:
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画 - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用
- menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭 - menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭
- radioGroup(props):单选组,options + value 信号双向绑定 - radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用
- card(props, ...children):卡片容器 - card(props, ...children):卡片容器
- badge(props):徽标,variant = default | success | warning | danger - badge(props):徽标,variant = default | success | warning | danger
- alert(props):提示条,variant = info | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger
+2 -2
View File
@@ -52,9 +52,9 @@
- textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用 - textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用
- checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用 - checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用
- switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用 - switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用
- select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画 - select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用
- menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭 - menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭
- radioGroup(props):单选组,options + value 信号双向绑定 - radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用
- card(props, ...children):卡片容器 - card(props, ...children):卡片容器
- badge(props):徽标,variant = default | success | warning | danger - badge(props):徽标,variant = default | success | warning | danger
- alert(props):提示条,variant = info | success | warning | danger - alert(props):提示条,variant = info | success | warning | danger
+6 -1
View File
@@ -6,7 +6,12 @@ export function radioGroup(props: RadioGroupProps): HTMLElement {
const groupName = `ua-radio-${Math.random().toString(36).slice(2, 8)}`; // 组内唯一 name const groupName = `ua-radio-${Math.random().toString(36).slice(2, 8)}`; // 组内唯一 name
const radios: HTMLInputElement[] = []; const radios: HTMLInputElement[] = [];
for (const opt of props.options) { 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; input.value = opt.value;
radios.push(input); radios.push(input);
input.addEventListener('change', () => { input.addEventListener('change', () => {
+2 -1
View File
@@ -24,6 +24,7 @@ export function select(props: SelectProps): HTMLElement {
class: 'ua-select-trigger', class: 'ua-select-trigger',
'aria-haspopup': 'listbox', 'aria-haspopup': 'listbox',
'aria-expanded': () => String(open()), 'aria-expanded': () => String(open()),
disabled: props.disabled, // 布尔或信号禁用,h() 直接透传(原生 button 拦截点击)
onClick: () => open(!open()), onClick: () => open(!open()),
}, },
h('span', { class: () => `ua-select-value${value() ? '' : ' is-empty'}` }, () => current() || props.placeholder || ''), 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 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) => { root.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') open(false); // Esc 关闭弹层 if (e.key === 'Escape') open(false); // Esc 关闭弹层
+2
View File
@@ -67,6 +67,7 @@ export interface SelectProps {
options: OptionItem[]; // 选项列表 options: OptionItem[]; // 选项列表
value?: Signal<string>; // 选中值信号,双向绑定 value?: Signal<string>; // 选中值信号,双向绑定
placeholder?: string; // 未选中时的占位项文案 placeholder?: string; // 未选中时的占位项文案
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
onChange?: (value: string) => void; // 变更回调 onChange?: (value: string) => void; // 变更回调
} }
@@ -74,6 +75,7 @@ export interface SelectProps {
export interface RadioGroupProps { export interface RadioGroupProps {
options: OptionItem[]; // 选项列表 options: OptionItem[]; // 选项列表
value?: Signal<string>; // 选中值信号,双向绑定 value?: Signal<string>; // 选中值信号,双向绑定
disabled?: boolean | Signal<boolean>; // 禁用态:整组置灰且不可选
onChange?: (value: string) => void; // 变更回调 onChange?: (value: string) => void; // 变更回调
} }
+14 -3
View File
@@ -18,9 +18,9 @@
"textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用", "textarea(props):多行输入,value 信号双向绑定,disabled 支持布尔或信号禁用",
"checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用", "checkbox(props):复选框,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用", "switchButton(props):开关,checked 信号双向绑定,disabled 支持布尔或信号禁用",
"select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画", "select(props):自绘下拉选择(非原生 select),value 信号双向绑定,点外部 / Esc 关闭,选项弹层带微动画,disabled 支持布尔或信号禁用",
"menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭", "menu(props):下拉菜单,触发器 + 自绘弹层,条目支持禁用 / 危险 / 分隔线,↑↓ 环形导航,点外部 / Esc 关闭",
"radioGroup(props):单选组,options + value 信号双向绑定", "radioGroup(props):单选组,options + value 信号双向绑定,disabled 整组禁用",
"card(props, ...children):卡片容器", "card(props, ...children):卡片容器",
"badge(props):徽标,variant = default | success | warning | danger", "badge(props):徽标,variant = default | success | warning | danger",
"alert(props):提示条,variant = info | success | warning | danger", "alert(props):提示条,variant = info | success | warning | danger",
@@ -280,6 +280,11 @@
"t": "string", "t": "string",
"d": "未选中时触发器显示的占位文案" "d": "未选中时触发器显示的占位文案"
}, },
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "禁用态:布尔直接置位,传信号则动态同步;禁用时触发器置灰且不展开弹层"
},
{ {
"n": "onChange", "n": "onChange",
"t": "(value: string) => void", "t": "(value: string) => void",
@@ -356,6 +361,11 @@
"t": "Signal<string>", "t": "Signal<string>",
"d": "选中值信号,双向绑定" "d": "选中值信号,双向绑定"
}, },
{
"n": "disabled",
"t": "boolean | Signal<boolean>",
"d": "整组禁用:全部选项与文案置灰且不可选"
},
{ {
"n": "onChange", "n": "onChange",
"t": "(value: string) => void", "t": "(value: string) => void",
@@ -367,7 +377,8 @@
"steps": [ "steps": [
"创建字符串信号,初值设为默认选项的 value", "创建字符串信号,初值设为默认选项的 value",
"传入 options 与 value,组内自动互斥", "传入 options 与 value,组内自动互斥",
"读取 size() 获取当前选中值" "读取 size() 获取当前选中值",
"需要整组禁用时传 disabled:布尔值静态禁用,也可传信号(如 locked)实现动态锁定"
] ]
}, },
{ {
+12 -1
View File
@@ -852,9 +852,20 @@
/* —— 通用禁用态:置灰 + 禁用光标 —— */ /* —— 通用禁用态:置灰 + 禁用光标 —— */
.ua-btn:disabled, .ua-btn:disabled,
.ua-checkbox:disabled, .ua-checkbox:disabled,
.ua-radio:disabled,
.ua-switch-input:disabled, .ua-switch-input:disabled,
.ua-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; opacity: 0.45;
cursor: not-allowed; cursor: not-allowed;
} }