iter(g159): 多选下拉支持标签折叠
This commit is contained in:
+8
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "ui-arch-registry-index/0.1",
|
"$schema": "ui-arch-registry-index/0.1",
|
||||||
"generatedAt": "2026-10-26T08:00:04.262Z",
|
"generatedAt": "2026-10-26T08:02:08.033Z",
|
||||||
"repoRoot": ".",
|
"repoRoot": ".",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
@@ -2987,6 +2987,11 @@
|
|||||||
"t": "number",
|
"t": "number",
|
||||||
"d": "最多选几项:到上限后未选项自动禁用"
|
"d": "最多选几项:到上限后未选项自动禁用"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"n": "maxTagCount",
|
||||||
|
"t": "number",
|
||||||
|
"d": "标签区最多显示几个,超出的折成一枚「+N」(悬停看全量、点击展开弹层);缺省全部显示"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"n": "searchable",
|
"n": "searchable",
|
||||||
"t": "boolean",
|
"t": "boolean",
|
||||||
@@ -3009,7 +3014,8 @@
|
|||||||
"点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)",
|
"点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)",
|
||||||
"点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点",
|
"点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点",
|
||||||
"↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)",
|
"↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)",
|
||||||
"value 传数组信号即受控,外部写入会同步标签区"
|
"value 传数组信号即受控,外部写入会同步标签区",
|
||||||
|
"选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+14
-1
@@ -228,6 +228,7 @@ const slideIndex = signal(0); // 轮播当前下标(外部受控演示)
|
|||||||
const cascaderPath = signal<string[]>(['east', 'sh']); // 级联选择路径(外部受控演示)
|
const cascaderPath = signal<string[]>(['east', 'sh']); // 级联选择路径(外部受控演示)
|
||||||
const splitSizes = signal<number[]>([35, 65]); // 分栏比例(外部受控演示)
|
const splitSizes = signal<number[]>([35, 65]); // 分栏比例(外部受控演示)
|
||||||
const tagPick = signal<string[]>(['ts']); // 多选下拉选中值(外部受控演示)
|
const tagPick = signal<string[]>(['ts']); // 多选下拉选中值(外部受控演示)
|
||||||
|
const tagFold = signal<string[]>(['ts', 'dom', 'css']); // 多选下拉折叠演示:预置 3 项看溢出
|
||||||
const permPick = signal<string[]>(['read']); // 多选组选中值(外部受控演示)
|
const permPick = signal<string[]>(['read']); // 多选组选中值(外部受控演示)
|
||||||
const colorVal = signal('var(--ua-color-accent)'); // 颜色选择值(外部受控演示)
|
const colorVal = signal('var(--ua-color-accent)'); // 颜色选择值(外部受控演示)
|
||||||
const dateVal = signal('2026-10-26'); // 日期选择值(外部受控演示)
|
const dateVal = signal('2026-10-26'); // 日期选择值(外部受控演示)
|
||||||
@@ -882,7 +883,7 @@ const content = h(
|
|||||||
section(
|
section(
|
||||||
'sec-multiselect',
|
'sec-multiselect',
|
||||||
'多选下拉',
|
'多选下拉',
|
||||||
'multiSelect:标签式回显 + 可搜索弹层,点选不收起弹层(连续多选),空输入退格删末个标签,max 到限后未选项禁用。',
|
'multiSelect:标签式回显 + 可搜索弹层,点选不收起弹层(连续多选),空输入退格删末个标签,max 到限后未选项禁用,maxTagCount 折叠溢出标签。',
|
||||||
h(
|
h(
|
||||||
'div',
|
'div',
|
||||||
{ class: 'row' },
|
{ class: 'row' },
|
||||||
@@ -900,6 +901,18 @@ const content = h(
|
|||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
h('span', { class: 'out' }, () => `已选:${tagPick().join(' / ') || '无'}`),
|
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(
|
section(
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
# @ui-arch 组件库 Agent 文档(无图形版)
|
# @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)。
|
> 组件 = 普通函数,传 props 返回 DOM;状态用 signal 双向绑定:读 signal() 写 signal(v)。
|
||||||
|
|
||||||
## 快速上手
|
## 快速上手
|
||||||
@@ -1590,6 +1590,7 @@ affix({ offset: 8 }, card({ title: '滚动时我会贴顶' }, h('p', null, '正
|
|||||||
- value:Signal<string[]> —— 选中值数组信号;不传则内部维护
|
- value:Signal<string[]> —— 选中值数组信号;不传则内部维护
|
||||||
- placeholder:string —— 未选时占位文案;已选时让位给标签
|
- placeholder:string —— 未选时占位文案;已选时让位给标签
|
||||||
- max:number —— 最多选几项:到上限后未选项自动禁用
|
- max:number —— 最多选几项:到上限后未选项自动禁用
|
||||||
|
- maxTagCount:number —— 标签区最多显示几个,超出的折成一枚「+N」(悬停看全量、点击展开弹层);缺省全部显示
|
||||||
- searchable:boolean —— 是否可输入过滤;缺省 true
|
- searchable:boolean —— 是否可输入过滤;缺省 true
|
||||||
- disabled:boolean | Signal<boolean> —— 禁用整个下拉
|
- disabled:boolean | Signal<boolean> —— 禁用整个下拉
|
||||||
- onChange:(values: string[]) => void —— 变更回调(传副本)
|
- onChange:(values: string[]) => void —— 变更回调(传副本)
|
||||||
@@ -1607,6 +1608,7 @@ multiSelect({ value: tagPick, max: 3, placeholder: '选择技术栈', options: [
|
|||||||
3. 点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点
|
3. 点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点
|
||||||
4. ↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)
|
4. ↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)
|
||||||
5. value 传数组信号即受控,外部写入会同步标签区
|
5. value 传数组信号即受控,外部写入会同步标签区
|
||||||
|
6. 选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算
|
||||||
|
|
||||||
现场演示:apps/docs/demo.html#sec-multiselect
|
现场演示:apps/docs/demo.html#sec-multiselect
|
||||||
|
|
||||||
|
|||||||
@@ -60,11 +60,17 @@ export function multiSelect(props: MultiSelectProps): HTMLElement {
|
|||||||
const render = (): void => {
|
const render = (): void => {
|
||||||
const cur = value();
|
const cur = value();
|
||||||
const full = props.max != null && cur.length >= props.max; // 是否已达上限
|
const full = props.max != null && cur.length >= props.max; // 是否已达上限
|
||||||
const tags = cur.map((v) => {
|
const cut = props.maxTagCount != null && cur.length > props.maxTagCount ? cur.slice(0, props.maxTagCount) : cur; // 折叠后仍显示的标签
|
||||||
const label = props.options.find((o) => o.value === v)?.label ?? v;
|
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) }, '×');
|
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);
|
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); // 只换标签,输入框保持挂载(重建会丢焦点与输入内容)
|
tagWrap.replaceChildren(...tags); // 只换标签,输入框保持挂载(重建会丢焦点与输入内容)
|
||||||
input.placeholder = cur.length ? '' : props.placeholder ?? '请选择'; // 有标签时不挤占占位文案
|
input.placeholder = cur.length ? '' : props.placeholder ?? '请选择'; // 有标签时不挤占占位文案
|
||||||
const rows = visible().map((o, i) => {
|
const rows = visible().map((o, i) => {
|
||||||
|
|||||||
@@ -700,6 +700,7 @@ export interface MultiSelectProps {
|
|||||||
value?: Signal<string[]>; // 选中值数组信号;不传则内部维护
|
value?: Signal<string[]>; // 选中值数组信号;不传则内部维护
|
||||||
placeholder?: string; // 未选时占位文案;缺省「请选择」
|
placeholder?: string; // 未选时占位文案;缺省「请选择」
|
||||||
max?: number; // 最多选几项:到上限后未选项禁用
|
max?: number; // 最多选几项:到上限后未选项禁用
|
||||||
|
maxTagCount?: number; // 标签区最多显示几个,超出折成「+N」;缺省全显示
|
||||||
searchable?: boolean; // 是否可输入过滤;缺省 true
|
searchable?: boolean; // 是否可输入过滤;缺省 true
|
||||||
disabled?: boolean | Signal<boolean>; // 禁用整个下拉
|
disabled?: boolean | Signal<boolean>; // 禁用整个下拉
|
||||||
onChange?: (values: string[]) => void; // 变更回调(传副本)
|
onChange?: (values: string[]) => void; // 变更回调(传副本)
|
||||||
|
|||||||
@@ -2858,6 +2858,11 @@
|
|||||||
"t": "number",
|
"t": "number",
|
||||||
"d": "最多选几项:到上限后未选项自动禁用"
|
"d": "最多选几项:到上限后未选项自动禁用"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"n": "maxTagCount",
|
||||||
|
"t": "number",
|
||||||
|
"d": "标签区最多显示几个,超出的折成一枚「+N」(悬停看全量、点击展开弹层);缺省全部显示"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"n": "searchable",
|
"n": "searchable",
|
||||||
"t": "boolean",
|
"t": "boolean",
|
||||||
@@ -2880,7 +2885,8 @@
|
|||||||
"点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)",
|
"点输入区展开弹层并聚焦输入框,输入字符即时过滤(大小写不敏感)",
|
||||||
"点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点",
|
"点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点",
|
||||||
"↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)",
|
"↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)",
|
||||||
"value 传数组信号即受控,外部写入会同步标签区"
|
"value 传数组信号即受控,外部写入会同步标签区",
|
||||||
|
"选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3611,6 +3611,16 @@ button.ua-step-dot:hover {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
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 {
|
.ua-multiselect-x {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user