iter(g111): 面包屑支持超长路径折叠
This commit is contained in:
@@ -1244,6 +1244,14 @@
|
|||||||
"acceptance": "SelectProps 新增 clearable(缺省 false):有值时触发器右缘出现 ×,点它把值写成空串并回调 onChange('');收起态按 Delete / Backspace 等价清空;按钮不进 Tab 序、无值自动隐藏;文档参数表 / 手把手 / api 说明与演示(城市下拉开启清空)同步;npm run check 全绿",
|
"acceptance": "SelectProps 新增 clearable(缺省 false):有值时触发器右缘出现 ×,点它把值写成空串并回调 onChange('');收起态按 Delete / Backspace 等价清空;按钮不进 Tab 序、无值自动隐藏;文档参数表 / 手把手 / api 说明与演示(城市下拉开启清空)同步;npm run check 全绿",
|
||||||
"notes": "自补:可用性;复用既有 value 信号与 onChange,不新增状态",
|
"notes": "自补:可用性;复用既有 value 信号与 onChange,不新增状态",
|
||||||
"status": "done"
|
"status": "done"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "g111",
|
||||||
|
"title": "面包屑支持超长路径折叠",
|
||||||
|
"motivation": "七层路径(首页/控制台/订单管理/华东区/2026 Q4/待发货/订单 #1024)在窄容器里会把末项当前页挤到换行甚至省略号之后,而当前页恰恰是最该看清的;breadcrumb 没有任何收窄手段",
|
||||||
|
"acceptance": "BreadcrumbProps 新增 maxItems:超出时渲染「首项 + … + 末 Math.max(2, maxItems-1) 项」,折叠按钮 aria-label 报出被折叠条数、点一下原地展开全部;展开后不再折叠,末项始终是当前页;文档参数表 / 手把手 / 演示(七层路径 maxItems=4)同步;npm run check 全绿",
|
||||||
|
"notes": "自补:可用性;渲染走 effect + replaceChildren,不重建整个 nav",
|
||||||
|
"status": "open"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T06:03:06.956Z",
|
"generatedAt": "2026-10-26T06:04:26.861Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -835,6 +835,11 @@
|
|||||||
"t": "(item, index) => void",
|
"t": "(item, index) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "无 href 条目的点击回调"
|
"d": "无 href 条目的点击回调"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "maxItems",
|
||||||
|
"t": "number",
|
||||||
|
"d": "最多展示几项:超出时首项之后折叠成「…」按钮(aria-label 报出折叠条数),点开即完整展开;缺省不折叠"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { label: '账户' }], onNavigate: (it) => console.log(it.label) })",
|
"example": "breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { label: '账户' }], onNavigate: (it) => console.log(it.label) })",
|
||||||
@@ -842,7 +847,8 @@
|
|||||||
"按层级顺序准备 items,最后一项即当前页,自动渲染为不可点的灰色文字并带 aria-current='page'",
|
"按层级顺序准备 items,最后一项即当前页,自动渲染为不可点的灰色文字并带 aria-current='page'",
|
||||||
"需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调",
|
"需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调",
|
||||||
"改分隔符传 separator,如 '›';分隔符对读屏隐藏",
|
"改分隔符传 separator,如 '›';分隔符对读屏隐藏",
|
||||||
"语义结构为 nav + ol + li,可直接放进页头或卡片标题栏"
|
"语义结构为 nav + ol + li,可直接放进页头或卡片标题栏",
|
||||||
|
"路径过深时传 maxItems(如 4):首项 + 「…」+ 末几项,点「…」原地展开全部,末项始终是当前页"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+10
-1
@@ -1100,12 +1100,21 @@ const content = h(
|
|||||||
section(
|
section(
|
||||||
'sec-breadcrumb',
|
'sec-breadcrumb',
|
||||||
'面包屑',
|
'面包屑',
|
||||||
'breadcrumb:nav + ol 语义结构,末项自动标为当前页,无 href 的条目走 onNavigate。',
|
'breadcrumb:nav + ol 语义结构,末项自动标为当前页,无 href 的条目走 onNavigate;条目超过 maxItems 时中段折叠成「…」,点开后完整展开。',
|
||||||
breadcrumb({
|
breadcrumb({
|
||||||
items: [{ label: '首页', href: '#sec-counter' }, { label: '组件库' }, { label: '导航类' }, { label: '面包屑' }],
|
items: [{ label: '首页', href: '#sec-counter' }, { label: '组件库' }, { label: '导航类' }, { label: '面包屑' }],
|
||||||
separator: '›',
|
separator: '›',
|
||||||
onNavigate: (item) => toast.info(`跳转到 ${item.label}`),
|
onNavigate: (item) => toast.info(`跳转到 ${item.label}`),
|
||||||
}),
|
}),
|
||||||
|
breadcrumb({
|
||||||
|
items: [
|
||||||
|
{ label: '首页', href: '#sec-counter' }, { label: '控制台' }, { label: '订单管理' }, { label: '华东区' },
|
||||||
|
{ label: '2026 Q4' }, { label: '待发货' }, { label: '订单 #1024' },
|
||||||
|
],
|
||||||
|
separator: '›',
|
||||||
|
maxItems: 4,
|
||||||
|
onNavigate: (item) => toast.info(`跳转到 ${item.label}`),
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
section(
|
section(
|
||||||
'sec-accordion',
|
'sec-accordion',
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @ui-arch 组件库 Agent 文档(无图形版)
|
||||||
|
|
||||||
> 自动生成于 2026-10-26T06:03:06.956Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
> 自动生成于 2026-10-26T06:04:26.861Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。
|
||||||
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -372,6 +372,7 @@ accordion({ items: [{ value: 'a', title: '账户设置', content: '内容…' },
|
|||||||
- items(必填):BreadcrumbItem[] —— 路径条目:{ label, href? },末项自动视为当前页
|
- items(必填):BreadcrumbItem[] —— 路径条目:{ label, href? },末项自动视为当前页
|
||||||
- separator(默认 /):string —— 分隔符;缺省 '/'
|
- separator(默认 /):string —— 分隔符;缺省 '/'
|
||||||
- onNavigate(回调):(item, index) => void —— 无 href 条目的点击回调
|
- onNavigate(回调):(item, index) => void —— 无 href 条目的点击回调
|
||||||
|
- maxItems:number —— 最多展示几项:超出时首项之后折叠成「…」按钮(aria-label 报出折叠条数),点开即完整展开;缺省不折叠
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
@@ -385,6 +386,7 @@ breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { labe
|
|||||||
2. 需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调
|
2. 需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调
|
||||||
3. 改分隔符传 separator,如 '›';分隔符对读屏隐藏
|
3. 改分隔符传 separator,如 '›';分隔符对读屏隐藏
|
||||||
4. 语义结构为 nav + ol + li,可直接放进页头或卡片标题栏
|
4. 语义结构为 nav + ol + li,可直接放进页头或卡片标题栏
|
||||||
|
5. 路径过深时传 maxItems(如 4):首项 + 「…」+ 末几项,点「…」原地展开全部,末项始终是当前页
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-breadcrumb
|
现场演示:apps/docs/demo.html#sec-breadcrumb
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,41 @@
|
|||||||
import { h } from '@ui-arch/frame';
|
import { effect, h, signal } from '@ui-arch/frame';
|
||||||
import type { BreadcrumbProps } from './types.js';
|
import type { BreadcrumbProps } from './types.js';
|
||||||
|
|
||||||
/*** 面包屑:nav + ol 语义结构,末项为当前页(不可点、aria-current);有 href 渲染 <a>,无 href 渲染 button 交 onNavigate ***/
|
/*** 面包屑:nav + ol 语义结构,末项为当前页(不可点、aria-current);条目超过 maxItems 时把中段折叠成「…」按钮 ***/
|
||||||
export function breadcrumb(props: BreadcrumbProps): HTMLElement {
|
export function breadcrumb(props: BreadcrumbProps): HTMLElement {
|
||||||
|
// —— 状态集中声明 ——
|
||||||
const items = props.items;
|
const items = props.items;
|
||||||
const sep = props.separator ?? '/';
|
const sep = props.separator ?? '/'; // 分隔符
|
||||||
|
const max = props.maxItems ?? Number.POSITIVE_INFINITY; // 展示条数上限:超出折叠中段
|
||||||
|
const expanded = signal(false); // 折叠段是否已展开(展开后不再折叠)
|
||||||
|
const list = h('ol', { class: 'ua-crumb-list' });
|
||||||
|
|
||||||
const entries = items.map((item, i) => {
|
/*** 逐项渲染:有 href 用 <a>,无 href 用 button 交 onNavigate;折叠按钮紧跟首项插入一次 ***/
|
||||||
const last = i === items.length - 1; // 末项即当前页
|
const render = (): void => {
|
||||||
const node = last
|
const fold = !expanded() && items.length > max; // 是否需要折叠
|
||||||
? h('span', { class: 'ua-crumb-current', 'aria-current': 'page' }, item.label)
|
const tail = Math.max(2, max - 1); // 尾部保留条数:至少当前页 + 上一级
|
||||||
: item.href
|
const kept = fold ? [0, ...items.slice(-tail).map((_, k) => items.length - tail + k)] : items.map((_, i) => i);
|
||||||
? h('a', { class: 'ua-crumb-link', href: item.href }, item.label)
|
const nodes: HTMLElement[] = [];
|
||||||
: h('button', { type: 'button', class: 'ua-crumb-link', onClick: () => props.onNavigate?.(item, i) }, item.label);
|
kept.forEach((idx, pos) => {
|
||||||
return h('li', { class: 'ua-crumb-item' }, node, last ? null : h('span', { class: 'ua-crumb-sep', 'aria-hidden': 'true' }, sep));
|
const item = items[idx];
|
||||||
});
|
const last = idx === items.length - 1; // 末项即当前页
|
||||||
|
const node = last
|
||||||
|
? h('span', { class: 'ua-crumb-current', 'aria-current': 'page' }, item.label)
|
||||||
|
: item.href
|
||||||
|
? h('a', { class: 'ua-crumb-link', href: item.href }, item.label)
|
||||||
|
: h('button', { type: 'button', class: 'ua-crumb-link', onClick: () => props.onNavigate?.(item, idx) }, item.label);
|
||||||
|
nodes.push(h('li', { class: 'ua-crumb-item' }, node, last ? null : h('span', { class: 'ua-crumb-sep', 'aria-hidden': 'true' }, sep)));
|
||||||
|
if (!fold || pos > 0) return; // 卫语句:折叠按钮只插一次
|
||||||
|
const hidden = items.length - kept.length; // 被折叠的条数
|
||||||
|
nodes.push(h('li', { class: 'ua-crumb-item' }, h(
|
||||||
|
'button',
|
||||||
|
{ type: 'button', class: 'ua-crumb-fold', 'aria-label': `展开折叠的 ${hidden} 项`, onClick: () => expanded(true) },
|
||||||
|
'…',
|
||||||
|
), h('span', { class: 'ua-crumb-sep', 'aria-hidden': 'true' }, sep)));
|
||||||
|
});
|
||||||
|
list.replaceChildren(...nodes);
|
||||||
|
};
|
||||||
|
|
||||||
return h('nav', { class: 'ua-breadcrumb', 'aria-label': '面包屑导航' }, h('ol', { class: 'ua-crumb-list' }, ...entries));
|
effect(render); // expanded 变化即重绘
|
||||||
|
return h('nav', { class: 'ua-breadcrumb', 'aria-label': '面包屑导航' }, list);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export interface BreadcrumbItem {
|
|||||||
export interface BreadcrumbProps {
|
export interface BreadcrumbProps {
|
||||||
items: BreadcrumbItem[]; // 路径条目,末项视为当前页(不可点)
|
items: BreadcrumbItem[]; // 路径条目,末项视为当前页(不可点)
|
||||||
separator?: string; // 分隔符;缺省 '/'
|
separator?: string; // 分隔符;缺省 '/'
|
||||||
|
maxItems?: number; // 最多展示几项;超出时中段折叠成「…」按钮,点开即完整展开;缺省不折叠
|
||||||
onNavigate?: (item: BreadcrumbItem, index: number) => void; // 无 href 条目的点击回调
|
onNavigate?: (item: BreadcrumbItem, index: number) => void; // 无 href 条目的点击回调
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -706,6 +706,11 @@
|
|||||||
"t": "(item, index) => void",
|
"t": "(item, index) => void",
|
||||||
"k": 1,
|
"k": 1,
|
||||||
"d": "无 href 条目的点击回调"
|
"d": "无 href 条目的点击回调"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"n": "maxItems",
|
||||||
|
"t": "number",
|
||||||
|
"d": "最多展示几项:超出时首项之后折叠成「…」按钮(aria-label 报出折叠条数),点开即完整展开;缺省不折叠"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"example": "breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { label: '账户' }], onNavigate: (it) => console.log(it.label) })",
|
"example": "breadcrumb({ items: [{ label: '首页', href: '/' }, { label: '设置' }, { label: '账户' }], onNavigate: (it) => console.log(it.label) })",
|
||||||
@@ -713,7 +718,8 @@
|
|||||||
"按层级顺序准备 items,最后一项即当前页,自动渲染为不可点的灰色文字并带 aria-current='page'",
|
"按层级顺序准备 items,最后一项即当前页,自动渲染为不可点的灰色文字并带 aria-current='page'",
|
||||||
"需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调",
|
"需要真实跳转就给条目加 href(渲染 <a>);单页应用想自行接管时不给 href,改用 onNavigate 回调",
|
||||||
"改分隔符传 separator,如 '›';分隔符对读屏隐藏",
|
"改分隔符传 separator,如 '›';分隔符对读屏隐藏",
|
||||||
"语义结构为 nav + ol + li,可直接放进页头或卡片标题栏"
|
"语义结构为 nav + ol + li,可直接放进页头或卡片标题栏",
|
||||||
|
"路径过深时传 maxItems(如 4):首项 + 「…」+ 末几项,点「…」原地展开全部,末项始终是当前页"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3784,3 +3784,23 @@ button.ua-step-dot:hover {
|
|||||||
.ua-select-clear[hidden] {
|
.ua-select-clear[hidden] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* —— 面包屑折叠按钮 —— */
|
||||||
|
.ua-crumb-fold {
|
||||||
|
padding: 0 2px;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
color: var(--ua-color-text-muted);
|
||||||
|
font-size: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--ua-radius-sm);
|
||||||
|
transition: color 0.15s, background-color 0.15s;
|
||||||
|
}
|
||||||
|
.ua-crumb-fold:hover {
|
||||||
|
color: var(--ua-color-accent);
|
||||||
|
background: color-mix(in srgb, var(--ua-color-accent) 8%, transparent);
|
||||||
|
}
|
||||||
|
.ua-crumb-fold:focus-visible {
|
||||||
|
outline: 2px solid var(--ua-color-accent);
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user