iter(g310): multiSelect 补全选与半选
This commit is contained in:
@@ -35,6 +35,19 @@ export function multiSelect(props: MultiSelectProps): HTMLElement {
|
||||
update([...cur]);
|
||||
};
|
||||
|
||||
/*** 全选 / 反选:只作用于当前过滤后的候选,上限时按顺序补到 max 为止 ***/
|
||||
const toggleAll = (): void => {
|
||||
const opts = visible().filter((o) => !o.disabled);
|
||||
const cur = new Set(value());
|
||||
const allOn = opts.length > 0 && opts.every((o) => cur.has(o.value));
|
||||
if (allOn) opts.forEach((o) => cur.delete(o.value)); // 全选态再点即清空这批
|
||||
else for (const o of opts) {
|
||||
if (props.max != null && cur.size >= props.max) break; // 到上限就停,不静默超选
|
||||
cur.add(o.value);
|
||||
}
|
||||
update([...cur]);
|
||||
};
|
||||
|
||||
const tagWrap = h('span', { class: 'ua-multiselect-tags' }) as HTMLElement; // 标签区:每次重绘只换这里
|
||||
const box = h('div', { class: 'ua-multiselect-box' }) as HTMLElement; // 标签 + 输入框容器
|
||||
const input = h('input', {
|
||||
@@ -76,6 +89,22 @@ export function multiSelect(props: MultiSelectProps): HTMLElement {
|
||||
tagWrap.replaceChildren(...tags); // 只换标签,输入框保持挂载(重建会丢焦点与输入内容)
|
||||
input.placeholder = cur.length ? '' : props.placeholder ?? '请选择'; // 有标签时不挤占占位文案
|
||||
const rows: HTMLElement[] = []; // 候选行:分组小标题与选项混排(小标题不可聚焦,不占 active 下标)
|
||||
if (props.selectAll) { // 全选行:占 active 下标 -1,键盘 ↑↓ 能走到,不用鼠标也能全选
|
||||
const opts = visible();
|
||||
const picked = opts.filter((o) => cur.includes(o.value)).length;
|
||||
const allOn = opts.length > 0 && picked === opts.length;
|
||||
rows.push(h(
|
||||
'li',
|
||||
{
|
||||
class: () => `ua-multiselect-opt ua-multiselect-all${open() && active() === -1 ? ' is-active' : ''}${opts.length ? '' : ' is-disabled'}`,
|
||||
role: 'option',
|
||||
'aria-selected': String(allOn),
|
||||
onmousedown: (e: Event) => { e.preventDefault(); if (opts.length) toggleAll(); }, // 阻止默认:保住输入焦点
|
||||
},
|
||||
h('span', { class: 'ua-multiselect-check', 'aria-hidden': 'true' }, allOn ? '✓' : picked ? '–' : ''), // – 为半选:部分可选
|
||||
h('span', {}, opts.length ? `全选(${picked}/${opts.length})` : '全选'),
|
||||
));
|
||||
}
|
||||
let prevGroup = ''; // 上一个候选的分组名,只在组首插一次标题
|
||||
visible().forEach((o, i) => {
|
||||
if (o.group && o.group !== prevGroup) rows.push(h('li', { class: 'ua-multiselect-group', role: 'presentation' }, o.group));
|
||||
@@ -101,14 +130,22 @@ export function multiSelect(props: MultiSelectProps): HTMLElement {
|
||||
box.addEventListener('click', () => { if (!(props.disabled === true)) { open(true); input.focus(); } }); // 点空白处展开并聚焦输入
|
||||
root.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
const opts = visible();
|
||||
const base = props.selectAll ? -1 : 0; // 全选行占 -1 号位:环形导航的起点随之左移一格
|
||||
const slots = opts.length + (props.selectAll ? 1 : 0); // 可停留的位置总数(含全选行)
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (!open()) { open(true); active(0); return; }
|
||||
if (!open()) { open(true); active(base); return; }
|
||||
if (!slots) return; // 无候选也没全选行:不动
|
||||
const dir = e.key === 'ArrowDown' ? 1 : -1;
|
||||
active((active() + dir + opts.length) % opts.length); // 环形移动
|
||||
active(base + (((active() - base) + dir + slots) % slots)); // 环形移动,越界回绕
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Enter' && open() && slots && active() >= base) {
|
||||
e.preventDefault();
|
||||
if (active() === -1) toggleAll(); // 全选行
|
||||
else toggle(opts[active()].value);
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Enter' && open() && active() >= 0) { e.preventDefault(); toggle(opts[active()].value); return; }
|
||||
if (e.key === 'Backspace' && !input.value && value().length) { update(value().slice(0, -1)); return; } // 空输入时退格删末个标签
|
||||
if (e.key === 'Escape') { open(false); return; }
|
||||
});
|
||||
@@ -120,7 +157,7 @@ export function multiSelect(props: MultiSelectProps): HTMLElement {
|
||||
else { releaseEsc?.(); releaseEsc = null; }
|
||||
});
|
||||
let releaseShift: (() => void) | null = null; // 展开期间的位移跟随释放函数(滚动 / 缩放重算翻转)
|
||||
effect(() => {
|
||||
effect(() => {
|
||||
if (!open()) { // 收起即摘监听:弹层不在,重算方位没有意义
|
||||
releaseShift?.();
|
||||
releaseShift = null;
|
||||
|
||||
Reference in New Issue
Block a user