iter(g166): 下拉选项支持分组标题
This commit is contained in:
@@ -73,10 +73,14 @@ export function multiSelect(props: MultiSelectProps): HTMLElement {
|
||||
}
|
||||
tagWrap.replaceChildren(...tags); // 只换标签,输入框保持挂载(重建会丢焦点与输入内容)
|
||||
input.placeholder = cur.length ? '' : props.placeholder ?? '请选择'; // 有标签时不挤占占位文案
|
||||
const rows = visible().map((o, i) => {
|
||||
const rows: HTMLElement[] = []; // 候选行:分组小标题与选项混排(小标题不可聚焦,不占 active 下标)
|
||||
let prevGroup = ''; // 上一个候选的分组名,只在组首插一次标题
|
||||
visible().forEach((o, i) => {
|
||||
if (o.group && o.group !== prevGroup) rows.push(h('li', { class: 'ua-multiselect-group', role: 'presentation' }, o.group));
|
||||
prevGroup = o.group ?? ''; // 无分组的候选打断连续段,后续同名组会重新起标题
|
||||
const on = cur.includes(o.value);
|
||||
const dead = o.disabled === true || (full && !on); // 到上限:未选项不可再点
|
||||
return h(
|
||||
rows.push(h(
|
||||
'li',
|
||||
{
|
||||
class: () => `ua-multiselect-opt${on ? ' is-selected' : ''}${active() === i ? ' is-active' : ''}${dead ? ' is-disabled' : ''}`,
|
||||
@@ -86,7 +90,7 @@ export function multiSelect(props: MultiSelectProps): HTMLElement {
|
||||
},
|
||||
h('span', { class: 'ua-multiselect-check', 'aria-hidden': 'true' }, on ? '✓' : ''),
|
||||
h('span', {}, o.label),
|
||||
);
|
||||
));
|
||||
});
|
||||
if (!rows.length) rows.push(h('li', { class: 'ua-multiselect-empty' }, '无匹配项'));
|
||||
list.replaceChildren(...rows);
|
||||
|
||||
@@ -60,6 +60,19 @@ export function select(props: SelectProps): HTMLElement {
|
||||
opt.label,
|
||||
),
|
||||
);
|
||||
// —— 分组:相邻同组选项合成一段,组内首项前插小标题;记下区间供过滤时整组显隐 ——
|
||||
const nodes: HTMLElement[] = []; // 弹层内容序列(小标题与选项混排)
|
||||
const runs: { el: HTMLElement; from: number; to: number }[] = []; // 每段标题及其首尾选项下标
|
||||
props.options.forEach((opt, i) => {
|
||||
let run = runs[runs.length - 1] ?? null;
|
||||
if (opt.group && (!run || run.to !== i - 1)) { // 新起一段:标题插在本组首项之前
|
||||
const el = h('div', { class: 'ua-option-group', role: 'presentation' }, opt.group);
|
||||
run = { el, from: i, to: i };
|
||||
runs.push(run);
|
||||
nodes.push(el);
|
||||
} else if (opt.group && run) run.to = i; // 同组续接:只拉长区间
|
||||
nodes.push(options[i]);
|
||||
});
|
||||
const search = h('input', {
|
||||
type: 'search',
|
||||
class: 'ua-select-search',
|
||||
@@ -75,7 +88,7 @@ export function select(props: SelectProps): HTMLElement {
|
||||
'div',
|
||||
{ class: () => `ua-select-pop${open() ? ' ua-open' : ''}${place() === 'top' ? ' is-flip' : ''}`, role: 'listbox' },
|
||||
props.searchable ? search : null,
|
||||
...options,
|
||||
...nodes,
|
||||
blank,
|
||||
);
|
||||
|
||||
@@ -129,6 +142,11 @@ export function select(props: SelectProps): HTMLElement {
|
||||
el.hidden = !on; // 不匹配即折叠,键盘只在可见项之间移动
|
||||
if (on) shown += 1;
|
||||
});
|
||||
for (const run of runs) { // 整组都无命中时连小标题一起藏,不留孤零零的分组名
|
||||
let any = false;
|
||||
for (let i = run.from; i <= run.to; i++) if (match(i)) { any = true; break; }
|
||||
run.el.hidden = !any;
|
||||
}
|
||||
blank.hidden = shown > 0 || !q; // 仅「有关键字且无命中」时才提示
|
||||
});
|
||||
effect(() => {
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface OptionItem {
|
||||
value: string; // 选项值(提交/匹配用)
|
||||
label: string; // 选项文案(展示用)
|
||||
disabled?: boolean; // tabs / segmented 消费:禁用该选项
|
||||
group?: string; // select 消费:分组小标题,相邻同组选项合成一段(组内首项前插标题)
|
||||
}
|
||||
|
||||
/*** 分段控制器入参 ***/
|
||||
@@ -687,6 +688,7 @@ export interface CheckOption {
|
||||
value: string; // 选项值
|
||||
label: string; // 显示文案
|
||||
disabled?: boolean; // 单项禁用:不可勾选,也不计入全选
|
||||
group?: string; // 分组小标题:相邻同组选项合成一段(组内首项前插标题),过滤后整组无命中则标题一并隐藏
|
||||
}
|
||||
|
||||
/*** 多选组入参 ***/
|
||||
|
||||
@@ -574,7 +574,7 @@
|
||||
"n": "options",
|
||||
"t": "OptionItem[]",
|
||||
"r": 1,
|
||||
"d": "选项列表,每项 { value, label }"
|
||||
"d": "选项列表,每项 { value, label };每项可加 group 指定分组小标题"
|
||||
},
|
||||
{
|
||||
"n": "value",
|
||||
@@ -616,7 +616,8 @@
|
||||
"选中项在弹层中用淡色弱化标记,不抢视觉焦点",
|
||||
"弹层展开时按 Esc 只收起下拉并回焦触发器;若下拉在弹窗/抽屉内,再按一次 Esc 才关外层(逐层关闭)",
|
||||
"searchable: true 时打开即聚焦搜索框,输入实时过滤;↑↓/Home/End 只在可见项间移动,无命中显示「无匹配选项」",
|
||||
"需要允许「不选」的筛选项就开 clearable:有值时右缘出现 ×,点它把值写成空串并回调 onChange(''),键盘在收起态按 Delete / Backspace 等价"
|
||||
"需要允许「不选」的筛选项就开 clearable:有值时右缘出现 ×,点它把值写成空串并回调 onChange(''),键盘在收起态按 Delete / Backspace 等价",
|
||||
"候选多时给选项加 group:相邻同组合成一段、组首插灰色小标题(段间有分隔线),过滤后整组无命中时标题一起隐藏——搜索路径与键盘 ↑↓ 仍只走真实选项"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2885,7 +2886,7 @@
|
||||
"n": "options",
|
||||
"t": "CheckOption[]",
|
||||
"r": 1,
|
||||
"d": "选项集合:value / label / 可选 disabled"
|
||||
"d": "选项集合:value / label / 可选 disabled;每项可加 group 指定分组小标题"
|
||||
},
|
||||
{
|
||||
"n": "value",
|
||||
@@ -2930,7 +2931,8 @@
|
||||
"点选项只切换该项、弹层不收起,适合连续多选;到 max 后未选项置灰不可点",
|
||||
"↑↓ 环形移动高亮、Enter 切换当前项、Esc 收起(与其它弹层共用一个 Esc 栈)",
|
||||
"value 传数组信号即受控,外部写入会同步标签区",
|
||||
"选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算"
|
||||
"选中项多时传 maxTagCount: 2 折叠:前 2 枚标签照常逐个删除,其余折成虚线「+N」,悬停 title 给出被折叠的项名,点它直接展开弹层;受控数组一变折叠数就重算",
|
||||
"候选分组同样用 group 字段:小标题是不可点击的 li(不占键盘高亮下标),选中后的标签顺序仍按点击顺序而非分组顺序"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -407,6 +407,20 @@ body.ua-col-resizing {
|
||||
color: var(--ua-color-text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
.ua-option-group {
|
||||
padding: 6px 10px 2px;
|
||||
color: var(--ua-color-text-muted);
|
||||
font-size: var(--ua-font-size-sm);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
.ua-option-group:not(:first-child) {
|
||||
margin-top: 4px;
|
||||
border-top: 1px solid var(--ua-color-border);
|
||||
padding-top: 8px;
|
||||
}
|
||||
.ua-option {
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -3767,6 +3781,14 @@ button.ua-step-dot:hover {
|
||||
overflow: auto;
|
||||
list-style: none;
|
||||
}
|
||||
.ua-multiselect-group {
|
||||
padding: 6px var(--ua-space-1) 2px;
|
||||
color: var(--ua-color-text-muted);
|
||||
font-size: var(--ua-font-size-sm);
|
||||
font-weight: 600;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
.ua-multiselect-opt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user