iter(g105): 标签页支持可关闭页签
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
import { effect, h } from '@ui-arch/frame';
|
||||
import type { TabsProps } from './types.js';
|
||||
import type { TabItem, TabsProps } from './types.js';
|
||||
|
||||
/*** 标签页:active 信号双向绑定 + 滑动指示条 + 方向键环形导航;内容由调用方按 active 派生 ***/
|
||||
/*** 标签页:active 信号双向绑定 + 滑动指示条 + 方向键环形导航;closable 页签带关闭按钮(聚焦后 Delete 亦可关) ***/
|
||||
export function tabs(props: TabsProps): HTMLElement {
|
||||
// —— 状态集中声明 ——
|
||||
const buttons: HTMLButtonElement[] = []; // 页签按钮,顺序与 tabs 一致
|
||||
const buttons: HTMLButtonElement[] = []; // 页签按钮,顺序与 tabs 一致(键盘导航与激活态都按它走)
|
||||
const indicator = h('span', { class: 'ua-tabs-indicator' });
|
||||
const head = h('div', { class: 'ua-tabs-head', role: 'tablist', 'aria-label': '标签页' }, indicator);
|
||||
|
||||
/*** 同步激活态与指示条几何位置 ***/
|
||||
/*** 同步激活态与指示条几何位置:量外壳而非按钮,可关闭页签的指示条才会盖住关闭按钮 ***/
|
||||
const sync = () => {
|
||||
const value = props.active?.();
|
||||
for (const btn of buttons) {
|
||||
@@ -16,13 +16,19 @@ export function tabs(props: TabsProps): HTMLElement {
|
||||
btn.classList.toggle('ua-tab-active', on);
|
||||
btn.setAttribute('aria-selected', String(on));
|
||||
btn.tabIndex = on ? 0 : -1; // 漫游 tabindex:仅激活页签可 Tab 聚焦
|
||||
if (on) {
|
||||
indicator.style.left = `${btn.offsetLeft}px`;
|
||||
indicator.style.width = `${btn.offsetWidth}px`;
|
||||
}
|
||||
if (!on) continue; // 卫语句:只有激活项需要摆指示条
|
||||
const wrap = btn.parentElement ?? btn;
|
||||
indicator.style.left = `${wrap.offsetLeft}px`;
|
||||
indicator.style.width = `${wrap.offsetWidth}px`;
|
||||
}
|
||||
};
|
||||
|
||||
/*** 关闭页签:只回调,数据由调用方移除后重绘,组件不自己改 tabs 数组 ***/
|
||||
const closeTab = (tab: TabItem, e: Event): void => {
|
||||
e.stopPropagation(); // 关闭按钮是页签按钮的兄弟节点,显式阻断以防外壳级监听误触
|
||||
props.onClose?.(tab.value);
|
||||
};
|
||||
|
||||
for (const tab of props.tabs) {
|
||||
const btn = h('button', { class: 'ua-tab', role: 'tab', type: 'button' }, tab.label) as HTMLButtonElement;
|
||||
btn.dataset.value = tab.value;
|
||||
@@ -33,8 +39,15 @@ export function tabs(props: TabsProps): HTMLElement {
|
||||
if (props.active) props.active(tab.value); // 用户切换写回信号
|
||||
props.onChange?.(tab.value);
|
||||
});
|
||||
btn.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if (e.key !== 'Delete' || !tab.closable) return; // 卫语句:只接管可关闭页签的 Delete
|
||||
e.preventDefault();
|
||||
props.onClose?.(tab.value); // 键盘等价:不必伸手去点 ×
|
||||
});
|
||||
const wrap = h('span', { class: 'ua-tab-item', role: 'presentation' }, btn); // 外壳只做布局,tab 语义仍在按钮上
|
||||
if (tab.closable) wrap.append(h('button', { type: 'button', class: 'ua-tab-close', 'aria-label': `关闭 ${tab.label}`, onClick: (e: Event) => closeTab(tab, e) }, '×'));
|
||||
buttons.push(btn);
|
||||
head.append(btn);
|
||||
head.append(wrap);
|
||||
}
|
||||
|
||||
head.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user