iter(g83): 开关改 switch 语义并补可访问名
This commit is contained in:
@@ -1020,6 +1020,14 @@
|
|||||||
"acceptance": "新增 maxSize(字节)/ maxCount(多选上限)两个可选参数;accept 支持 .ext / type/subtype / type/* 且对拖拽同样生效;被拒文件不进清单并在 role=status 提示行写明原因(类型不符 / 超上限 / 数量已满);不改onChange 语义;文档与演示同步;npm run check 全绿",
|
"acceptance": "新增 maxSize(字节)/ maxCount(多选上限)两个可选参数;accept 支持 .ext / type/subtype / type/* 且对拖拽同样生效;被拒文件不进清单并在 role=status 提示行写明原因(类型不符 / 超上限 / 数量已满);不改onChange 语义;文档与演示同步;npm run check 全绿",
|
||||||
"notes": "自补:可用性;校验入口收敛到 add(),点选与拖拽共用同一条路径",
|
"notes": "自补:可用性;校验入口收敛到 add(),点选与拖拽共用同一条路径",
|
||||||
"status": "done"
|
"status": "done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g83",
|
||||||
|
"title": "开关改用 switch 语义并补可访问名兜底",
|
||||||
|
"motivation": "开关底层是 input type=checkbox,读屏一律播报成「复选框」,与视觉上的开关语义不符;不留 label 时更是完全没有可访问名(只有一个匿名复选框)",
|
||||||
|
"acceptance": "input 补 role=switch(选中态沿用原生 checked 映射);新增可选 ariaLabel,缺省取 label,纯图标开关也有可访问名;文档 props 与演示(无文案开关实例)同步;npm run check 全绿",
|
||||||
|
"notes": "自补:无障碍;沿用原生 checkbox 的 Space 切换行为,不重造键盘交互",
|
||||||
|
"status": "open"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T05:00:02.358Z",
|
"generatedAt": "2026-10-26T05:00:45.858Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -607,6 +607,11 @@
|
|||||||
"t": "Signal<boolean>",
|
"t": "Signal<boolean>",
|
||||||
"d": "开关状态信号,双向绑定"
|
"d": "开关状态信号,双向绑定"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"n": "ariaLabel",
|
||||||
|
"t": "string",
|
||||||
|
"d": "无可见文案时的可访问名(如纯图标开关);缺省取 label"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"n": "label",
|
"n": "label",
|
||||||
"t": "string",
|
"t": "string",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ const name = signal('');
|
|||||||
// —— 表单控件状态集中声明 ——
|
// —— 表单控件状态集中声明 ——
|
||||||
const agreed = signal(false);
|
const agreed = signal(false);
|
||||||
const nightMode = signal(true);
|
const nightMode = signal(true);
|
||||||
|
const sound = signal(true); // 无可见文案的开关演示
|
||||||
const city = signal('');
|
const city = signal('');
|
||||||
const size = signal('m');
|
const size = signal('m');
|
||||||
const bio = signal('');
|
const bio = signal('');
|
||||||
@@ -254,6 +255,7 @@ const content = h(
|
|||||||
'checkbox / switch / select / radioGroup / textarea:状态一律由调用方信号持有;select 弹层在视口下方空间不足时自动翻到上方,展开后键入字母可快跳选项;textarea 传 maxCount 显示实时字数。',
|
'checkbox / switch / select / radioGroup / textarea:状态一律由调用方信号持有;select 弹层在视口下方空间不足时自动翻到上方,展开后键入字母可快跳选项;textarea 传 maxCount 显示实时字数。',
|
||||||
h('div', { class: 'row' }, checkbox({ label: '同意条款', checked: agreed }), h('span', { class: 'out' }, () => (agreed() ? '已同意' : '未同意'))),
|
h('div', { class: 'row' }, checkbox({ label: '同意条款', checked: agreed }), h('span', { class: 'out' }, () => (agreed() ? '已同意' : '未同意'))),
|
||||||
h('div', { class: 'row' }, switchButton({ label: '夜间模式', checked: nightMode }), h('span', { class: 'out' }, () => (nightMode() ? '开' : '关'))),
|
h('div', { class: 'row' }, switchButton({ label: '夜间模式', checked: nightMode }), h('span', { class: 'out' }, () => (nightMode() ? '开' : '关'))),
|
||||||
|
h('div', { class: 'row' }, switchButton({ checked: sound, ariaLabel: '通知声音' }), h('span', { class: 'out' }, () => `无可见文案的开关(ariaLabel 兜底,读屏念「通知声音」):${sound() ? '开' : '关'}`)),
|
||||||
h('div', { class: 'row' }, select({ options: cities.map((c) => ({ value: c, label: c })), value: city, placeholder: '选择城市…' }), h('span', { class: 'out' }, () => (city() ? `城市:${city()}` : '未选择'))),
|
h('div', { class: 'row' }, select({ options: cities.map((c) => ({ value: c, label: c })), value: city, placeholder: '选择城市…' }), h('span', { class: 'out' }, () => (city() ? `城市:${city()}` : '未选择'))),
|
||||||
h('div', { class: 'row' }, radioGroup({ options: [{ value: 's', label: '小' }, { value: 'm', label: '中' }, { value: 'l', label: '大' }], value: size })),
|
h('div', { class: 'row' }, radioGroup({ options: [{ value: 's', label: '小' }, { value: 'm', label: '中' }, { value: 'l', label: '大' }], value: size })),
|
||||||
textarea({ placeholder: '一句话介绍…', value: bio, rows: 2, maxCount: 30 }),
|
textarea({ placeholder: '一句话介绍…', value: bio, rows: 2, maxCount: 30 }),
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T05:00:02.358Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T05:00:45.858Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -239,6 +239,7 @@ const agreed = signal(false); checkbox({ label: '同意条款', checked: agreed,
|
|||||||
参数:
|
参数:
|
||||||
|
|
||||||
- checked:Signal<boolean> —— 开关状态信号,双向绑定
|
- checked:Signal<boolean> —— 开关状态信号,双向绑定
|
||||||
|
- ariaLabel:string —— 无可见文案时的可访问名(如纯图标开关);缺省取 label
|
||||||
- label:string —— 左侧文案
|
- label:string —— 左侧文案
|
||||||
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
- disabled:boolean | Signal<boolean> —— 禁用态:布尔直接置位,传信号则动态同步
|
||||||
- onChange(回调):(checked: boolean) => void —— 变更回调
|
- onChange(回调):(checked: boolean) => void —— 变更回调
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ import type { SwitchProps } from './types.js';
|
|||||||
|
|
||||||
/*** 开关:checked 信号双向绑定,disabled 支持布尔或信号禁用,轨道 + 滑块的纯 CSS 实现 ***/
|
/*** 开关:checked 信号双向绑定,disabled 支持布尔或信号禁用,轨道 + 滑块的纯 CSS 实现 ***/
|
||||||
export function switchButton(props: SwitchProps): HTMLElement {
|
export function switchButton(props: SwitchProps): HTMLElement {
|
||||||
const input = h('input', { type: 'checkbox', class: 'ua-switch-input', disabled: props.disabled }) as HTMLInputElement; // false/undefined 移除属性,Signal 走响应式
|
const input = h('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
class: 'ua-switch-input',
|
||||||
|
role: 'switch', // 用开关语义而非复选框:读屏播报「开关」并跟随选中态
|
||||||
|
'aria-label': props.ariaLabel ?? props.label, // 无可见文案时也要有可访问名
|
||||||
|
disabled: props.disabled, // false/undefined 移除属性,Signal 走响应式
|
||||||
|
}) as HTMLInputElement;
|
||||||
if (props.checked) {
|
if (props.checked) {
|
||||||
const checked = props.checked;
|
const checked = props.checked;
|
||||||
input.addEventListener('change', () => checked(input.checked)); // 用户切换写回信号
|
input.addEventListener('change', () => checked(input.checked)); // 用户切换写回信号
|
||||||
|
|||||||
@@ -91,7 +91,8 @@ export interface CheckboxProps {
|
|||||||
|
|
||||||
/*** 开关入参 ***/
|
/*** 开关入参 ***/
|
||||||
export interface SwitchProps {
|
export interface SwitchProps {
|
||||||
label?: string; // 左侧文案;缺省不渲染
|
label?: string; // 左侧文案;缺省不渲染(此时读屏名靠 ariaLabel 兜底)
|
||||||
|
ariaLabel?: string; // 无可见文案时的可访问名,如纯图标开关;缺省取 label
|
||||||
checked?: Signal<boolean>; // 开关状态信号,双向绑定
|
checked?: Signal<boolean>; // 开关状态信号,双向绑定
|
||||||
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
|
||||||
onChange?: (checked: boolean) => void; // 变更回调
|
onChange?: (checked: boolean) => void; // 变更回调
|
||||||
|
|||||||
@@ -478,6 +478,11 @@
|
|||||||
"t": "Signal<boolean>",
|
"t": "Signal<boolean>",
|
||||||
"d": "开关状态信号,双向绑定"
|
"d": "开关状态信号,双向绑定"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"n": "ariaLabel",
|
||||||
|
"t": "string",
|
||||||
|
"d": "无可见文案时的可访问名(如纯图标开关);缺省取 label"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"n": "label",
|
"n": "label",
|
||||||
"t": "string",
|
"t": "string",
|
||||||
|
|||||||
Reference in New Issue
Block a user