import { effect, h, signal } from '@ui-arch/frame'; import type { CarouselProps } from './types.js'; /*** 轮播:items 横排 + translateX 位移;指示点 / 左右箭头 / ←→ 键切换,autoplay 毫秒自动前进且悬停暂停 ***/ export function carousel(props: CarouselProps): HTMLElement { // —— 状态集中声明 —— const total = props.items.length; const index = props.index ?? signal(0); // 未传外部信号时用内部信号,行为一致 const wrap = (i: number) => (total ? ((i % total) + total) % total : 0); // 环形取模,负数也回绕 const dots: HTMLButtonElement[] = []; // 指示点,顺序即下标 const track = h('div', { class: 'ua-carousel-track' }) as HTMLElement; // 幻灯片轨道,靠 transform 位移 const dotsBar = h('div', { class: 'ua-carousel-dots' }) as HTMLElement; /*** 跳到指定下标(自动环形),并回传 onChange ***/ const go = (i: number) => { if (!total) return; // 卫语句:无内容不动作 index(wrap(i)); props.onChange?.(index()); }; props.items.forEach((item, i) => { track.append(h('div', { class: 'ua-carousel-slide', role: 'group', 'aria-roledescription': '幻灯片', 'aria-label': `第 ${i + 1} 张,共 ${total} 张` }, item)); const dot = h('button', { type: 'button', class: 'ua-carousel-dot', 'aria-label': `跳到第 ${i + 1} 张`, onClick: () => go(i) }) as HTMLButtonElement; dots.push(dot); dotsBar.append(dot); }); /*** 生成左右箭头 ***/ const arrow = (dir: 1 | -1, label: string) => h('button', { type: 'button', class: `ua-carousel-arrow ua-carousel-${dir === 1 ? 'next' : 'prev'}`, 'aria-label': label, onClick: () => go(index() + dir) }, dir === 1 ? '›' : '‹'); const root = h( 'div', { class: 'ua-carousel', tabindex: '0', role: 'region', 'aria-roledescription': '轮播', 'aria-label': props.label ?? '轮播' }, track, arrow(-1, '上一张'), arrow(1, '下一张'), dotsBar, ) as HTMLElement; if (props.height) root.style.height = `${props.height}px`; // 固定高度由调用方指定,缺省随内容 effect(() => { const i = wrap(index()); track.style.transform = `translateX(${-i * 100}%)`; // 整屏位移,配合 CSS transition 出滑动效果 dots.forEach((dot, k) => { dot.classList.toggle('ua-carousel-dot-active', k === i); dot.setAttribute('aria-current', String(k === i)); // 读屏标注当前张 }); }); root.addEventListener('keydown', (e: KeyboardEvent) => { if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return; // 卫语句:只接管方向键 e.preventDefault(); e.stopPropagation(); // 不冒泡:避免外层 tabs/menu 同时响应 go(index() + (e.key === 'ArrowRight' ? 1 : -1)); }); if (props.autoplay && total > 1) { let hover = false; // 悬停中 let focused = false; // 焦点在轮播内(键盘用户没有 hover,必须单独计) root.addEventListener('pointerenter', () => { hover = true; }); root.addEventListener('pointerleave', () => { hover = false; }); root.addEventListener('focusin', () => { focused = true; }); root.addEventListener('focusout', () => { focused = false; }); window.setInterval(() => { if (!hover && !focused) go(index() + 1); }, props.autoplay); // 两者都不在才前进 } return root; }