import { effect, h } from '@ui-arch/frame'; import type { TabsProps } from './types.js'; /*** 标签页:active 信号双向绑定 + 滑动指示条 + 方向键环形导航;内容由调用方按 active 派生 ***/ export function tabs(props: TabsProps): HTMLElement { // —— 状态集中声明 —— const buttons: HTMLButtonElement[] = []; // 页签按钮,顺序与 tabs 一致 const indicator = h('span', { class: 'ua-tabs-indicator' }); const head = h('div', { class: 'ua-tabs-head', role: 'tablist' }, indicator); /*** 同步激活态与指示条几何位置 ***/ const sync = () => { const value = props.active?.(); for (const btn of buttons) { const on = btn.dataset.value === value; 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`; } } }; 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; btn.disabled = tab.disabled ?? false; btn.addEventListener('click', () => { if (btn.disabled) return; // 卫语句:禁用页签不响应 if (props.active) props.active(tab.value); // 用户切换写回信号 props.onChange?.(tab.value); }); buttons.push(btn); head.append(btn); } head.addEventListener('keydown', (e: KeyboardEvent) => { const delta = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0; if (!delta) return; // 卫语句:非方向键放行 const list = buttons.filter((b) => !b.disabled); const i = list.indexOf(document.activeElement as HTMLButtonElement); if (i < 0) return; // 卫语句:焦点不在页签上 e.preventDefault(); const next = list[(i + delta + list.length) % list.length]; // 环形移动 next.focus(); next.click(); }); effect(sync); requestAnimationFrame(sync); // 挂载后再量一次布局,否则指示条宽度为 0 return h('div', { class: 'ua-tabs' }, head); }