iter(g67): tag 支持 checkable 筛选片,选中态与 aria-pressed 同步
This commit is contained in:
+19
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T04:34:05.417Z",
|
"generatedAt": "2026-10-26T04:35:37.103Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -930,6 +930,22 @@
|
|||||||
"t": "() => void",
|
"t": "() => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "关闭回调:调用方从数据源移除该项后重新渲染"
|
"d": "关闭回调:调用方从数据源移除该项后重新渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "checkable",
|
||||||
|
"t": "boolean",
|
||||||
|
"df": "false",
|
||||||
|
"d": "可选中:渲染为筛选片(button + aria-pressed);与 closable 互斥,同时传以 checkable 为准"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "checked",
|
||||||
|
"t": "Signal<boolean>",
|
||||||
|
"d": "选中态信号,双向绑定(点击写回、外部赋值同步);不传则组件内部自持"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onChange",
|
||||||
|
"t": "(checked: boolean) => void",
|
||||||
|
"d": "选中态变化回调,参数是点击后的新值"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('前端') })",
|
"example": "tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('前端') })",
|
||||||
@@ -938,7 +954,8 @@
|
|||||||
"按语义选 variant:info 中性标记、success 通过、warning 待处理、danger 已失效",
|
"按语义选 variant:info 中性标记、success 通过、warning 待处理、danger 已失效",
|
||||||
"closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)",
|
"closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)",
|
||||||
"组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号",
|
"组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号",
|
||||||
"标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定"
|
"标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定",
|
||||||
|
"需要可点的筛选片时传 checkable + checked:点击写回信号,视觉与 aria-pressed 同步,不做整块重渲染(焦点不丢)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ const drawerOpen = signal(false);
|
|||||||
|
|
||||||
// —— 标签列表:关闭即写回信号,容器按信号重渲染 ——
|
// —— 标签列表:关闭即写回信号,容器按信号重渲染 ——
|
||||||
const tags = signal(['设计', '前端', '组件库']);
|
const tags = signal(['设计', '前端', '组件库']);
|
||||||
|
const chipOn = { 全部: signal(true), 进行中: signal(false), 已完成: signal(false), 已归档: signal(false) }; // 筛选片选中态:每片独立信号,供 tag 双向绑定
|
||||||
|
|
||||||
// —— 滑块:音量与禁用态演示 ——
|
// —— 滑块:音量与禁用态演示 ——
|
||||||
const volume = signal(40);
|
const volume = signal(40);
|
||||||
@@ -306,6 +307,12 @@ const content = h(
|
|||||||
renderList(box, () => tags().map((t) => tag({ label: t, variant: 'info', closable: true, onClose: () => tags(tags().filter((x) => x !== t)) })));
|
renderList(box, () => tags().map((t) => tag({ label: t, variant: 'info', closable: true, onClose: () => tags(tags().filter((x) => x !== t)) })));
|
||||||
return box;
|
return box;
|
||||||
})(),
|
})(),
|
||||||
|
(() => {
|
||||||
|
const chips = h('div', { class: 'row' });
|
||||||
|
renderList(chips, () => Object.entries(chipOn).map(([name, on]) => tag({ label: name, checkable: true, checked: on })));
|
||||||
|
const out = h('span', { class: 'out' }, () => `已选:${Object.entries(chipOn).filter(([, on]) => on()).map(([name]) => name).join(' / ') || '无'}`);
|
||||||
|
return h('div', {}, chips, out);
|
||||||
|
})(),
|
||||||
),
|
),
|
||||||
section(
|
section(
|
||||||
'sec-avatar',
|
'sec-avatar',
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T04:34:05.417Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T04:35:37.103Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -442,6 +442,9 @@ badge({ label: '成功', variant: 'success' })
|
|||||||
- variant(默认 default):'default' | 'success' | 'warning' | 'danger' | 'info' —— 语义色;缺省 default
|
- variant(默认 default):'default' | 'success' | 'warning' | 'danger' | 'info' —— 语义色;缺省 default
|
||||||
- closable(默认 false):boolean —— 显示 × 关闭按钮;缺省 false(纯展示)
|
- closable(默认 false):boolean —— 显示 × 关闭按钮;缺省 false(纯展示)
|
||||||
- onClose(回调):() => void —— 关闭回调:调用方从数据源移除该项后重新渲染
|
- onClose(回调):() => void —— 关闭回调:调用方从数据源移除该项后重新渲染
|
||||||
|
- checkable(默认 false):boolean —— 可选中:渲染为筛选片(button + aria-pressed);与 closable 互斥,同时传以 checkable 为准
|
||||||
|
- checked:Signal<boolean> —— 选中态信号,双向绑定(点击写回、外部赋值同步);不传则组件内部自持
|
||||||
|
- onChange:(checked: boolean) => void —— 选中态变化回调,参数是点击后的新值
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
@@ -456,6 +459,7 @@ tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('
|
|||||||
3. closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)
|
3. closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)
|
||||||
4. 组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号
|
4. 组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号
|
||||||
5. 标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定
|
5. 标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定
|
||||||
|
6. 需要可点的筛选片时传 checkable + checked:点击写回信号,视觉与 aria-pressed 同步,不做整块重渲染(焦点不丢)
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-tag
|
现场演示:apps/docs/demo.html#sec-tag
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
import { h } from '@ui-arch/frame';
|
import { effect, h, signal } from '@ui-arch/frame';
|
||||||
import type { TagProps } from './types.js';
|
import type { TagProps } from './types.js';
|
||||||
|
|
||||||
/*** 标签:语义色小胶囊,可选 × 关闭;与 badge 的分工是 tag 可交互(带关闭回调) ***/
|
/*** 标签:语义色小胶囊;checkable 时作筛选片(aria-pressed + 信号双向绑定),否则可选 × 关闭 ***/
|
||||||
export function tag(props: TagProps): HTMLElement {
|
export function tag(props: TagProps): HTMLElement {
|
||||||
const label = h('span', { class: 'ua-tag-label' }, props.label);
|
const label = h('span', { class: 'ua-tag-label' }, props.label);
|
||||||
if (!props.closable) {
|
const base = `ua-tag ua-tag-${props.variant ?? 'default'}`; // 语义色共用
|
||||||
return h('span', { class: `ua-tag ua-tag-${props.variant ?? 'default'}` }, label); // 纯展示形态
|
if (props.checkable) {
|
||||||
|
const on = props.checked ?? signal(false); // 未受控时内部自持,行为与受控一致
|
||||||
|
const btn = h('button', { type: 'button', class: `${base} is-checkable`, 'aria-pressed': String(on()) }, label) as HTMLButtonElement;
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
on(!on()); // 用户点击写回信号
|
||||||
|
props.onChange?.(on());
|
||||||
|
});
|
||||||
|
effect(() => {
|
||||||
|
const value = on();
|
||||||
|
btn.setAttribute('aria-pressed', String(value)); // 读屏与视觉同源
|
||||||
|
btn.classList.toggle('is-checked', value);
|
||||||
|
});
|
||||||
|
return btn;
|
||||||
}
|
}
|
||||||
|
if (!props.closable) return h('span', { class: base }, label); // 纯展示形态
|
||||||
return h(
|
return h(
|
||||||
'span',
|
'span',
|
||||||
{ class: `ua-tag is-closable ua-tag-${props.variant ?? 'default'}` },
|
{ class: `${base} is-closable` },
|
||||||
label,
|
label,
|
||||||
h('button', { type: 'button', class: 'ua-tag-close', 'aria-label': `移除 ${props.label}`, onClick: () => props.onClose?.() }, '×'),
|
h('button', { type: 'button', class: 'ua-tag-close', 'aria-label': `移除 ${props.label}`, onClick: () => props.onClose?.() }, '×'),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -254,6 +254,9 @@ export interface TagProps {
|
|||||||
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info'; // 语义色;缺省 default
|
variant?: 'default' | 'success' | 'warning' | 'danger' | 'info'; // 语义色;缺省 default
|
||||||
closable?: boolean; // 显示 × 关闭按钮;缺省 false
|
closable?: boolean; // 显示 × 关闭按钮;缺省 false
|
||||||
onClose?: () => void; // 关闭回调(调用方自行从数据源移除该项)
|
onClose?: () => void; // 关闭回调(调用方自行从数据源移除该项)
|
||||||
|
checkable?: boolean; // 可选中(当筛选片用);与 closable 互斥,同时传时以 checkable 为准
|
||||||
|
checked?: Signal<boolean>; // 选中态信号,双向绑定;不传则组件内部自持
|
||||||
|
onChange?: (checked: boolean) => void; // 选中态变化回调(点一下即回调新值)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** 图片入参 ***/
|
/*** 图片入参 ***/
|
||||||
|
|||||||
@@ -801,6 +801,22 @@
|
|||||||
"t": "() => void",
|
"t": "() => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "关闭回调:调用方从数据源移除该项后重新渲染"
|
"d": "关闭回调:调用方从数据源移除该项后重新渲染"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "checkable",
|
||||||
|
"t": "boolean",
|
||||||
|
"df": "false",
|
||||||
|
"d": "可选中:渲染为筛选片(button + aria-pressed);与 closable 互斥,同时传以 checkable 为准"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "checked",
|
||||||
|
"t": "Signal<boolean>",
|
||||||
|
"d": "选中态信号,双向绑定(点击写回、外部赋值同步);不传则组件内部自持"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "onChange",
|
||||||
|
"t": "(checked: boolean) => void",
|
||||||
|
"d": "选中态变化回调,参数是点击后的新值"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('前端') })",
|
"example": "tag({ label: '前端', variant: 'info', closable: true, onClose: () => remove('前端') })",
|
||||||
@@ -809,7 +825,8 @@
|
|||||||
"按语义选 variant:info 中性标记、success 通过、warning 待处理、danger 已失效",
|
"按语义选 variant:info 中性标记、success 通过、warning 待处理、danger 已失效",
|
||||||
"closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)",
|
"closable: true 时必须配 onClose:把数据源里的该项删掉再重渲染(组件无状态,不自行摘除节点)",
|
||||||
"组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号",
|
"组合用法:容器里用 renderList 按信号数组渲染 tag 列表,删除即写回信号",
|
||||||
"标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定"
|
"标签是内联元素,可直接排进 .row 等容器,自动换行由容器决定",
|
||||||
|
"需要可点的筛选片时传 checkable + checked:点击写回信号,视觉与 aria-pressed 同步,不做整块重渲染(焦点不丢)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -708,6 +708,37 @@
|
|||||||
transform: scale(1.15);
|
transform: scale(1.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* —— 可选标签(筛选片)—— */
|
||||||
|
.ua-tag.is-checkable {
|
||||||
|
font: inherit;
|
||||||
|
color: var(--ua-color-text-muted);
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.15s, border-color 0.15s, background 0.15s, color 0.15s;
|
||||||
|
}
|
||||||
|
.ua-tag.is-checkable:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
color: var(--ua-color-text);
|
||||||
|
}
|
||||||
|
.ua-tag.is-checkable:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
.ua-tag.is-checkable:focus-visible {
|
||||||
|
outline: 2px solid color-mix(in srgb, var(--ua-color-accent) 45%, transparent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
.ua-tag.is-checked {
|
||||||
|
border-color: color-mix(in srgb, var(--ua-color-accent) 55%, transparent);
|
||||||
|
background: color-mix(in srgb, var(--ua-color-accent) 10%, var(--ua-color-bg));
|
||||||
|
color: var(--ua-color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.ua-tag.is-checkable { transition: none; }
|
||||||
|
.ua-tag.is-checkable:hover,
|
||||||
|
.ua-tag.is-checkable:active { transform: none; }
|
||||||
|
}
|
||||||
|
|
||||||
/* —— 滑块 —— */
|
/* —— 滑块 —— */
|
||||||
.ua-slider {
|
.ua-slider {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user