Files
UI/packages/skin/ui/src/pagination.ts
T

43 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { h, renderList } from '@ui-arch/frame';
import type { PaginationProps } from './types.js';
/*** 页码序列:≤7 页全展开;否则保留首尾与当前页 ±1 窗口,缺口折叠为省略号(null 占位) ***/
function pageSequence(page: number, totalPages: number): (number | null)[] {
if (totalPages <= 7) return Array.from({ length: totalPages }, (_, i) => i + 1);
const marks = [1, 2, page - 1, page, page + 1, totalPages - 1, totalPages].filter((p) => p >= 1 && p <= totalPages);
const seq: (number | null)[] = [];
let prev = 0;
for (const p of [...new Set(marks)].sort((a, b) => a - b)) {
if (p - prev > 1) seq.push(null); // 缺口 → 省略号
seq.push(p);
prev = p;
}
return seq;
}
/*** 独立分页器:受控组件,page 为双向绑定信号;页码过多折叠省略号,首末页禁用翻页,原生 button 保证键盘可达 ***/
export function pagination(props: PaginationProps): HTMLElement {
const totalPages = Math.max(1, Math.ceil(props.total / props.pageSize)); // 总页数
const go = (p: number) => {
const next = Math.min(totalPages, Math.max(1, p)); // 钳制到有效页码范围
props.page(next);
props.onChange?.(next);
};
const nav = h('nav', { class: 'ua-pagination', 'aria-label': '分页' });
renderList(nav, () => {
const page = Math.min(totalPages, Math.max(1, props.page()));
const items: HTMLElement[] = [h('button', { class: 'ua-page-btn', disabled: page <= 1, 'aria-label': '上一页', onClick: () => go(page - 1) }, '‹')];
for (const it of pageSequence(page, totalPages)) {
items.push(
it == null
? h('span', { class: 'ua-page-gap', 'aria-hidden': 'true' }, '…')
: h('button', { class: `ua-page-btn${it === page ? ' ua-active' : ''}`, disabled: it === page, 'aria-current': it === page ? 'page' : null, 'aria-label': `第 ${it} 页`, onClick: () => go(it) }, String(it)),
);
}
items.push(h('button', { class: 'ua-page-btn', disabled: page >= totalPages, 'aria-label': '下一页', onClick: () => go(page + 1) }, '›'));
return items;
});
return nav;
}