62 lines
2.2 KiB
TypeScript
62 lines
2.2 KiB
TypeScript
import { effect, h, signal } from '@ui-arch/frame';
|
|
import type { SelectProps } from './types.js';
|
|
|
|
/*** 下拉选择:自绘弹层替代原生 select,value 信号双向绑定;点选项 / 点外部 / Esc 关闭 ***/
|
|
export function select(props: SelectProps): HTMLElement {
|
|
// —— 状态集中声明 ——
|
|
const open = signal(false); // 弹层开合
|
|
const value = props.value ?? signal(''); // 选中值;未传信号则内部持有
|
|
|
|
const labelOf = (v: string) => props.options.find((o) => o.value === v)?.label ?? '';
|
|
const current = () => labelOf(value()); // 触发器显示文案(响应式)
|
|
|
|
const pick = (v: string) => {
|
|
value(v);
|
|
open(false);
|
|
props.onChange?.(v);
|
|
};
|
|
|
|
// —— 触发器 ——
|
|
const trigger = h(
|
|
'button',
|
|
{
|
|
type: 'button',
|
|
class: 'ua-select-trigger',
|
|
'aria-haspopup': 'listbox',
|
|
'aria-expanded': () => String(open()),
|
|
disabled: props.disabled, // 布尔或信号禁用,h() 直接透传(原生 button 拦截点击)
|
|
onClick: () => open(!open()),
|
|
},
|
|
h('span', { class: () => `ua-select-value${value() ? '' : ' is-empty'}` }, () => current() || props.placeholder || ''),
|
|
h('span', { class: 'ua-select-arrow' }, '▾'),
|
|
);
|
|
|
|
// —— 选项弹层 ——
|
|
const options = props.options.map((opt) =>
|
|
h(
|
|
'button',
|
|
{
|
|
type: 'button',
|
|
class: () => `ua-option${opt.value === value() ? ' ua-option-selected' : ''}`,
|
|
role: 'option',
|
|
'aria-selected': () => String(opt.value === value()),
|
|
onClick: () => pick(opt.value),
|
|
},
|
|
opt.label,
|
|
),
|
|
);
|
|
const pop = h('div', { class: () => `ua-select-pop${open() ? ' ua-open' : ''}`, role: 'listbox' }, ...options);
|
|
|
|
const root = h('div', { class: () => `ua-select${open() ? ' ua-open' : ''}` }, trigger, pop); // 根带开合类,供箭头旋转
|
|
|
|
root.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
if (e.key === 'Escape') open(false); // Esc 关闭弹层
|
|
});
|
|
document.addEventListener('click', (e) => {
|
|
if (root.contains(e.target as Node)) return; // 卫语句:组件内点击不处理
|
|
open(false); // 点外部关闭
|
|
});
|
|
effect(() => open()); // 建立依赖,保证开合信号生效
|
|
return root;
|
|
}
|