From 312e80118f6e74c54cbdd10576b9435e69950d2f Mon Sep 17 00:00:00 2001 From: HIsheR Date: Mon, 26 Oct 2026 16:02:24 +0800 Subject: [PATCH] =?UTF-8?q?iter(g159):=20=E5=A4=9A=E9=80=89=E4=B8=8B?= =?UTF-8?q?=E6=8B=89=E6=94=AF=E6=8C=81=E6=A0=87=E7=AD=BE=E6=8A=98=E5=8F=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .ua/index.json | 10 ++++++++-- apps/demo/main.ts | 15 ++++++++++++++- llms-full.txt | 4 +++- packages/skin/ui/src/multiSelect.ts | 10 ++++++++-- packages/skin/ui/src/types.ts | 1 + packages/skin/ui/ua.manifest.json | 8 +++++++- packages/skin/ui/ui.css | 10 ++++++++++ 7 files changed, 51 insertions(+), 7 deletions(-) diff --git a/.ua/index.json b/.ua/index.json index 5a60844..1e2b6de 100644 --- a/.ua/index.json +++ b/.ua/index.json @@ -1,6 +1,6 @@ { "$schema": "ui-arch-registry-index/0.1", - "generatedAt": "2026-10-26T08:00:04.262Z", + "generatedAt": "2026-10-26T08:02:08.033Z", "repoRoot": ".", "packages": [ { @@ -2987,6 +2987,11 @@ "t": "number", "d": "最多选几项:到上限后未选项自动禁用" }, + { + "n": "maxTagCount", + "t": "number", + "d": "标签区最多显示几个,超出的折成一枚「+N」(悬停看全量、点击展开弹层);缺省全部显示" + }, { "n": "searchable", "t": "boolean", @@ -3009,7 +3014,8 @@ "点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)", "点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点", "↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)", - "value 传数组信号即受控,外部写入会同步标签区" + "value 传数组信号即受控,外部写入会同步标签区", + "选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算" ] }, { diff --git a/apps/demo/main.ts b/apps/demo/main.ts index a78f2a1..4079898 100644 --- a/apps/demo/main.ts +++ b/apps/demo/main.ts @@ -228,6 +228,7 @@ const slideIndex = signal(0); // 轮播当前下标(外部受控演示) const cascaderPath = signal(['east', 'sh']); // 级联选择路径(外部受控演示) const splitSizes = signal([35, 65]); // 分栏比例(外部受控演示) const tagPick = signal(['ts']); // 多选下拉选中值(外部受控演示) +const tagFold = signal(['ts', 'dom', 'css']); // 多选下拉折叠演示:预置 3 项看溢出 const permPick = signal(['read']); // 多选组选中值(外部受控演示) const colorVal = signal('var(--ua-color-accent)'); // 颜色选择值(外部受控演示) const dateVal = signal('2026-10-26'); // 日期选择值(外部受控演示) @@ -882,7 +883,7 @@ const content = h( section( 'sec-multiselect', '多选下拉', - 'multiSelect:标签式回显 + 可搜索弹层,点选不收起弹层(连续多选),空输入退格删末个标签,max 到限后未选项禁用。', + 'multiSelect:标签式回显 + 可搜索弹层,点选不收起弹层(连续多选),空输入退格删末个标签,max 到限后未选项禁用,maxTagCount 折叠溢出标签。', h( 'div', { class: 'row' }, @@ -900,6 +901,18 @@ const content = h( ], }), h('span', { class: 'out' }, () => `已选:${tagPick().join(' / ') || '无'}`), + multiSelect({ + value: tagFold, + maxTagCount: 2, + placeholder: '标签折叠演示', + options: [ + { value: 'ts', label: 'TypeScript' }, + { value: 'dom', label: '原生 DOM' }, + { value: 'css', label: 'CSS 令牌' }, + { value: 'test', label: '测试' }, + ], + }), + h('span', { class: 'out' }, () => `折叠后仅露出 2 枚标签,其余折成 +N(共 ${tagFold().length} 项)`), ), ), section( diff --git a/llms-full.txt b/llms-full.txt index 1dd9cd0..8042ce0 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,6 +1,6 @@ # @ui-arch 组件库 Agent 文档(无图形版) -> 自动生成于 2026-10-26T08:00:04.262Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 +> 自动生成于 2026-10-26T08:02:08.033Z,数据源:各包 ua.manifest.json,勿手改。人类版文档站:apps/docs/index.html。 > 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。 ## 快速上手 @@ -1590,6 +1590,7 @@ affix({ offset: 8 }, card({ title: '滚动时我会贴顶' }, h('p', null, '正 - value:Signal —— 选中值数组信号;不传则内部维护 - placeholder:string —— 未选时占位文案;已选时让位给标签 - max:number —— 最多选几项:到上限后未选项自动禁用 +- maxTagCount:number —— 标签区最多显示几个,超出的折成一枚「+N」(悬停看全量、点击展开弹层);缺省全部显示 - searchable:boolean —— 是否可输入过滤;缺省 true - disabled:boolean | Signal —— 禁用整个下拉 - onChange:(values: string[]) => void —— 变更回调(传副本) @@ -1607,6 +1608,7 @@ multiSelect({ value: tagPick, max: 3, placeholder: '选择技术栈', options: [ 3. 点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点 4. ↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈) 5. value 传数组信号即受控,外部写入会同步标签区 +6. 选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算 现场演示:apps/docs/demo.html#sec-multiselect diff --git a/packages/skin/ui/src/multiSelect.ts b/packages/skin/ui/src/multiSelect.ts index 80815d3..68366ff 100644 --- a/packages/skin/ui/src/multiSelect.ts +++ b/packages/skin/ui/src/multiSelect.ts @@ -60,11 +60,17 @@ export function multiSelect(props: MultiSelectProps): HTMLElement { const render = (): void => { const cur = value(); const full = props.max != null && cur.length >= props.max; // 是否已达上限 - const tags = cur.map((v) => { - const label = props.options.find((o) => o.value === v)?.label ?? v; + const cut = props.maxTagCount != null && cur.length > props.maxTagCount ? cur.slice(0, props.maxTagCount) : cur; // 折叠后仍显示的标签 + const labelOf = (v: string): string => props.options.find((o) => o.value === v)?.label ?? v; + const tags = cut.map((v) => { + const label = labelOf(v); const del = h('button', { type: 'button', class: 'ua-multiselect-x', 'aria-label': `移除 ${label}`, onClick: () => toggle(v) }, '×'); return h('span', { class: 'ua-multiselect-tag' }, h('span', {}, label), del); }); + if (cut.length < cur.length) { // 溢出项折成一枚 +N,悬停给全量、点击展开弹层 + const rest = cur.slice(cut.length).map(labelOf).join('、'); + tags.push(h('span', { class: 'ua-multiselect-tag is-more', title: rest, 'aria-label': `另有 ${cur.length - cut.length} 项:${rest}`, onClick: () => open(true) }, `+${cur.length - cut.length}`)); + } tagWrap.replaceChildren(...tags); // 只换标签,输入框保持挂载(重建会丢焦点与输入内容) input.placeholder = cur.length ? '' : props.placeholder ?? '请选择'; // 有标签时不挤占占位文案 const rows = visible().map((o, i) => { diff --git a/packages/skin/ui/src/types.ts b/packages/skin/ui/src/types.ts index f3c4029..8ef8a09 100644 --- a/packages/skin/ui/src/types.ts +++ b/packages/skin/ui/src/types.ts @@ -700,6 +700,7 @@ export interface MultiSelectProps { value?: Signal; // 选中值数组信号;不传则内部维护 placeholder?: string; // 未选时占位文案;缺省「请选择」 max?: number; // 最多选几项:到上限后未选项禁用 + maxTagCount?: number; // 标签区最多显示几个,超出折成「+N」;缺省全显示 searchable?: boolean; // 是否可输入过滤;缺省 true disabled?: boolean | Signal; // 禁用整个下拉 onChange?: (values: string[]) => void; // 变更回调(传副本) diff --git a/packages/skin/ui/ua.manifest.json b/packages/skin/ui/ua.manifest.json index 03393f5..cb8db13 100644 --- a/packages/skin/ui/ua.manifest.json +++ b/packages/skin/ui/ua.manifest.json @@ -2858,6 +2858,11 @@ "t": "number", "d": "最多选几项:到上限后未选项自动禁用" }, + { + "n": "maxTagCount", + "t": "number", + "d": "标签区最多显示几个,超出的折成一枚「+N」(悬停看全量、点击展开弹层);缺省全部显示" + }, { "n": "searchable", "t": "boolean", @@ -2880,7 +2885,8 @@ "点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)", "点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点", "↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)", - "value 传数组信号即受控,外部写入会同步标签区" + "value 传数组信号即受控,外部写入会同步标签区", + "选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算" ] }, { diff --git a/packages/skin/ui/ui.css b/packages/skin/ui/ui.css index ba41b0f..082266d 100644 --- a/packages/skin/ui/ui.css +++ b/packages/skin/ui/ui.css @@ -3611,6 +3611,16 @@ button.ua-step-dot:hover { white-space: nowrap; text-overflow: ellipsis; } +.ua-multiselect-tag.is-more { + color: var(--ua-color-accent); + border: 1px dashed color-mix(in srgb, var(--ua-color-accent) 40%, transparent); + background: transparent; + cursor: pointer; + font-variant-numeric: tabular-nums; +} +.ua-multiselect-tag.is-more:hover { + background: color-mix(in srgb, var(--ua-color-accent) 12%, transparent); +} .ua-multiselect-x { padding: 0; font-size: 12px;