iter: d21 无障碍收尾(aria-label / role / aria-hidden)
This commit is contained in:
@@ -216,6 +216,14 @@
|
|||||||
"acceptance": "ProgressProps.value 改为可选;缺省时渲染不确定态(35% 填充块 1.2s 往复)、不显示百分比、role=progressbar + aria-label=进行中;定量态补 aria-valuemin/max/now;reduced-motion 静态化;demo 加不确定态示例;manifest/文档/llms-full.txt 同步;npm run check 全绿",
|
"acceptance": "ProgressProps.value 改为可选;缺省时渲染不确定态(35% 填充块 1.2s 往复)、不显示百分比、role=progressbar + aria-label=进行中;定量态补 aria-valuemin/max/now;reduced-motion 静态化;demo 加不确定态示例;manifest/文档/llms-full.txt 同步;npm run check 全绿",
|
||||||
"status": "done",
|
"status": "done",
|
||||||
"notes": "以「不传 value」作为不确定态的开关,不新增布尔参数(少一个可矛盾的状态);定量态的 aria-valuenow 用函数属性随信号更新;reduced-motion 下靠既有 .ua-progress-fill 规则把动画归零,退化为静态 35% 条"
|
"notes": "以「不传 value」作为不确定态的开关,不新增布尔参数(少一个可矛盾的状态);定量态的 aria-valuenow 用函数属性随信号更新;reduced-motion 下靠既有 .ua-progress-fill 规则把动画归零,退化为静态 35% 条"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "d21",
|
||||||
|
"title": "无障碍收尾:× 按钮 aria-label、spinner/skeleton 语义、tablist 标签",
|
||||||
|
"motivation": "审计发现 modal 的 × 按钮无 aria-label(读屏只念「×」)、spinner 无 role=status(不播报加载)、skeleton 未 aria-hidden(占位文本被读出来)、tablist 缺 aria-label 与 aria-disabled",
|
||||||
|
"acceptance": "modal 关闭按钮补 type/aria-label、spinner 补 role=status + aria-label、skeleton 全形态 aria-hidden、tabs 的 tablist 补 aria-label 且禁用页签标 aria-disabled;manifest conventions 记录无障碍基线;npm run check 全绿",
|
||||||
|
"status": "done",
|
||||||
|
"notes": "纯属性级修复,不动结构与样式;tabIndex 漫游(仅激活页签可 Tab)此前已实现,本次只补语义;分页器审计后无需改动(已有 nav aria-label、页码 aria-current、‹› aria-label)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-23T09:02:18.149Z",
|
"generatedAt": "2026-10-23T09:03:01.680Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -212,7 +212,8 @@
|
|||||||
"conventions": [
|
"conventions": [
|
||||||
"皮肤不持有业务状态,一律委托 headless 内核或调用方信号",
|
"皮肤不持有业务状态,一律委托 headless 内核或调用方信号",
|
||||||
"表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调",
|
"表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调",
|
||||||
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色"
|
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色",
|
||||||
|
"无障碍基线:装饰性占位(skeleton)aria-hidden、加载指示(spinner)role=status、关闭按钮必须 aria-label、弹层弹窗 aria-haspopup/expanded、读屏可见的状态一律同步 aria-*"
|
||||||
],
|
],
|
||||||
"docs": {
|
"docs": {
|
||||||
"intro": "27 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
|
"intro": "27 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
|
||||||
|
|||||||
@@ -25,3 +25,4 @@
|
|||||||
{"time": "2026-10-23T17:00:55+08:00", "id": "d18", "result": "done", "summary": "ua verify 新增皮肤硬编码颜色门禁(#hex 禁止,rgba 仅限阴影/遮罩),清掉 ui.css 5 处 #fff 改令牌;负向测试确认能拦;check 全绿"}
|
{"time": "2026-10-23T17:00:55+08:00", "id": "d18", "result": "done", "summary": "ua verify 新增皮肤硬编码颜色门禁(#hex 禁止,rgba 仅限阴影/遮罩),清掉 ui.css 5 处 #fff 改令牌;负向测试确认能拦;check 全绿"}
|
||||||
{"time": "2026-10-23T17:01:46+08:00", "id": "d19", "result": "done", "summary": "dataTable 新增 selected 受控信号:与内核选择双向同步(内容比较 + 拷贝保护),demo 展示已选计数与清空;check 全绿"}
|
{"time": "2026-10-23T17:01:46+08:00", "id": "d19", "result": "done", "summary": "dataTable 新增 selected 受控信号:与内核选择双向同步(内容比较 + 拷贝保护),demo 展示已选计数与清空;check 全绿"}
|
||||||
{"time": "2026-10-23T17:02:24+08:00", "id": "d20", "result": "done", "summary": "progress 支持不确定态:value 可选,缺省时 35% 填充块往复 + aria-label 进行中;定量态补 aria-valuenow;demo/manifest/llms 同步,check 全绿"}
|
{"time": "2026-10-23T17:02:24+08:00", "id": "d20", "result": "done", "summary": "progress 支持不确定态:value 可选,缺省时 35% 填充块往复 + aria-label 进行中;定量态补 aria-valuenow;demo/manifest/llms 同步,check 全绿"}
|
||||||
|
{"time": "2026-10-23T17:03:02+08:00", "id": "d21", "result": "done", "summary": "无障碍收尾:modal × 补 aria-label/type、spinner role=status、skeleton aria-hidden、tablist aria-label + 禁用页签 aria-disabled;conventions 记录基线;check 全绿"}
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-23T09:02:18.149Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-23T09:03:01.680Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -763,6 +763,7 @@ const checked = signal(false); checkbox({ label: '同意', checked }); const ope
|
|||||||
- 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
- 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
||||||
- 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
- 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
||||||
- 语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色
|
- 语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色
|
||||||
|
- 无障碍基线:装饰性占位(skeleton)aria-hidden、加载指示(spinner)role=status、关闭按钮必须 aria-label、弹层弹窗 aria-haspopup/expanded、读屏可见的状态一律同步 aria-*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@
|
|||||||
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
- 约定: 皮肤不持有业务状态,一律委托 headless 内核或调用方信号
|
||||||
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
- 约定: 表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调
|
||||||
- 约定: 语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色
|
- 约定: 语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色
|
||||||
|
- 约定: 无障碍基线:装饰性占位(skeleton)aria-hidden、加载指示(spinner)role=status、关闭按钮必须 aria-label、弹层弹窗 aria-haspopup/expanded、读屏可见的状态一律同步 aria-*
|
||||||
|
|
||||||
## @ui-arch/cli (v0.1.0)
|
## @ui-arch/cli (v0.1.0)
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function modal(props: ModalProps, ...children: unknown[]): HTMLElement {
|
|||||||
const box = h(
|
const box = h(
|
||||||
'div',
|
'div',
|
||||||
{ class: `ua-modal ua-modal-${props.size ?? 'md'}` },
|
{ class: `ua-modal ua-modal-${props.size ?? 'md'}` },
|
||||||
h('div', { class: 'ua-modal-head' }, h('h3', { class: 'ua-modal-title' }, props.title), h('button', { class: 'ua-modal-close', onClick: close }, '×')),
|
h('div', { class: 'ua-modal-head' }, h('h3', { class: 'ua-modal-title' }, props.title), h('button', { type: 'button', class: 'ua-modal-close', 'aria-label': '关闭弹窗', onClick: close }, '×')),
|
||||||
h('div', { class: 'ua-modal-body' }, ...children),
|
h('div', { class: 'ua-modal-body' }, ...children),
|
||||||
props.footer ? h('div', { class: 'ua-modal-foot' }, props.footer) : null, // 底部操作区:右对齐,仅传了才渲染
|
props.footer ? h('div', { class: 'ua-modal-foot' }, props.footer) : null, // 底部操作区:右对齐,仅传了才渲染
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import type { SkeletonProps } from './types.js';
|
|||||||
/*** 骨架屏:variant 决定占位形状(text 多行 / rect 块 / circle 圆),纯展示无状态 ***/
|
/*** 骨架屏:variant 决定占位形状(text 多行 / rect 块 / circle 圆),纯展示无状态 ***/
|
||||||
export function skeleton(props: SkeletonProps): HTMLElement {
|
export function skeleton(props: SkeletonProps): HTMLElement {
|
||||||
const variant = props.variant ?? 'rect'; // 缺省整块矩形
|
const variant = props.variant ?? 'rect'; // 缺省整块矩形
|
||||||
if (variant !== 'text') return h('div', { class: `ua-skeleton ua-skeleton-${variant}` }); // 单形状直接返回
|
if (variant !== 'text') return h('div', { class: `ua-skeleton ua-skeleton-${variant}`, 'aria-hidden': 'true' }); // 纯装饰,读屏跳过
|
||||||
const rows = props.rows ?? 3; // text 行数,末行缩短模拟段落收尾
|
const rows = props.rows ?? 3; // text 行数,末行缩短模拟段落收尾
|
||||||
const lines: HTMLElement[] = [];
|
const lines: HTMLElement[] = [];
|
||||||
for (let i = 0; i < rows; i++) lines.push(h('div', { class: `ua-skeleton ua-skeleton-text${i === rows - 1 ? ' ua-last' : ''}` }));
|
for (let i = 0; i < rows; i++) lines.push(h('div', { class: `ua-skeleton ua-skeleton-text${i === rows - 1 ? ' ua-last' : ''}`, 'aria-hidden': 'true' }));
|
||||||
return h('div', { class: 'ua-skeleton-group' }, ...lines);
|
return h('div', { class: 'ua-skeleton-group', 'aria-hidden': 'true' }, ...lines); // 整组装饰性占位
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { SpinnerProps } from './types.js';
|
|||||||
/*** 加载指示器:纯 CSS 旋转动画,size 控制直径 ***/
|
/*** 加载指示器:纯 CSS 旋转动画,size 控制直径 ***/
|
||||||
export function spinner(props: SpinnerProps = {}): HTMLElement {
|
export function spinner(props: SpinnerProps = {}): HTMLElement {
|
||||||
const size = `${props.size ?? 18}px`;
|
const size = `${props.size ?? 18}px`;
|
||||||
const el = h('span', { class: 'ua-spinner' });
|
const el = h('span', { class: 'ua-spinner', role: 'status', 'aria-label': '加载中' }); // 读屏播报加载状态
|
||||||
el.style.width = size;
|
el.style.width = size;
|
||||||
el.style.height = size;
|
el.style.height = size;
|
||||||
return el;
|
return el;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export function tabs(props: TabsProps): HTMLElement {
|
|||||||
// —— 状态集中声明 ——
|
// —— 状态集中声明 ——
|
||||||
const buttons: HTMLButtonElement[] = []; // 页签按钮,顺序与 tabs 一致
|
const buttons: HTMLButtonElement[] = []; // 页签按钮,顺序与 tabs 一致
|
||||||
const indicator = h('span', { class: 'ua-tabs-indicator' });
|
const indicator = h('span', { class: 'ua-tabs-indicator' });
|
||||||
const head = h('div', { class: 'ua-tabs-head', role: 'tablist' }, indicator);
|
const head = h('div', { class: 'ua-tabs-head', role: 'tablist', 'aria-label': '标签页' }, indicator);
|
||||||
|
|
||||||
/*** 同步激活态与指示条几何位置 ***/
|
/*** 同步激活态与指示条几何位置 ***/
|
||||||
const sync = () => {
|
const sync = () => {
|
||||||
@@ -27,6 +27,7 @@ export function tabs(props: TabsProps): HTMLElement {
|
|||||||
const btn = h('button', { class: 'ua-tab', role: 'tab', type: 'button' }, tab.label) as HTMLButtonElement;
|
const btn = h('button', { class: 'ua-tab', role: 'tab', type: 'button' }, tab.label) as HTMLButtonElement;
|
||||||
btn.dataset.value = tab.value;
|
btn.dataset.value = tab.value;
|
||||||
btn.disabled = tab.disabled ?? false;
|
btn.disabled = tab.disabled ?? false;
|
||||||
|
if (tab.disabled) btn.setAttribute('aria-disabled', 'true'); // 读屏提示不可选
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
if (btn.disabled) return; // 卫语句:禁用页签不响应
|
if (btn.disabled) return; // 卫语句:禁用页签不响应
|
||||||
if (props.active) props.active(tab.value); // 用户切换写回信号
|
if (props.active) props.active(tab.value); // 用户切换写回信号
|
||||||
|
|||||||
@@ -83,7 +83,8 @@
|
|||||||
"conventions": [
|
"conventions": [
|
||||||
"皮肤不持有业务状态,一律委托 headless 内核或调用方信号",
|
"皮肤不持有业务状态,一律委托 headless 内核或调用方信号",
|
||||||
"表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调",
|
"表单类组件统一约定:*Props.value/checked/active 为 Signal 双向绑定,onChange/onInput 为回调",
|
||||||
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色"
|
"语义色 variant 四档:default|info / success / warning / danger,一律来自令牌,禁止硬编码颜色",
|
||||||
|
"无障碍基线:装饰性占位(skeleton)aria-hidden、加载指示(spinner)role=status、关闭按钮必须 aria-label、弹层弹窗 aria-haspopup/expanded、读屏可见的状态一律同步 aria-*"
|
||||||
],
|
],
|
||||||
"docs": {
|
"docs": {
|
||||||
"intro": "27 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
|
"intro": "27 个 DOM 组件,统一用法:组件是普通函数,传入 props 返回 DOM 节点;需要状态时先创建 signal 再双向绑定。引入 CSS:import '@ui-arch/ui/ui.css'(内部已消费 tokens)。",
|
||||||
|
|||||||
Reference in New Issue
Block a user