@zag-js/pagination
Version:
Core logic for the pagination widget implemented as a state machine
319 lines (312 loc) • 10.2 kB
JavaScript
;
var anatomy$1 = require('@zag-js/anatomy');
var domQuery = require('@zag-js/dom-query');
var core = require('@zag-js/core');
var types = require('@zag-js/types');
var utils = require('@zag-js/utils');
// src/pagination.anatomy.ts
var anatomy = anatomy$1.createAnatomy("pagination").parts("root", "item", "ellipsis", "prevTrigger", "nextTrigger");
var parts = anatomy.build();
// src/pagination.dom.ts
var getRootId = (ctx) => ctx.ids?.root ?? `pagination:${ctx.id}`;
var getPrevTriggerId = (ctx) => ctx.ids?.prevTrigger ?? `pagination:${ctx.id}:prev`;
var getNextTriggerId = (ctx) => ctx.ids?.nextTrigger ?? `pagination:${ctx.id}:next`;
var getEllipsisId = (ctx, index) => ctx.ids?.ellipsis?.(index) ?? `pagination:${ctx.id}:ellipsis:${index}`;
var getItemId = (ctx, page) => ctx.ids?.item?.(page) ?? `pagination:${ctx.id}:item:${page}`;
// src/pagination.utils.ts
var range = (start, end) => {
let length = end - start + 1;
return Array.from({ length }, (_, idx) => idx + start);
};
var transform = (items) => {
return items.map((value) => {
if (typeof value === "number") return { type: "page", value };
return { type: "ellipsis" };
});
};
var ELLIPSIS = "ellipsis";
var getRange = (ctx) => {
const { page, totalPages, siblingCount } = ctx;
const totalPageNumbers = Math.min(2 * siblingCount + 5, totalPages);
const firstPageIndex = 1;
const lastPageIndex = totalPages;
const leftSiblingIndex = Math.max(page - siblingCount, firstPageIndex);
const rightSiblingIndex = Math.min(page + siblingCount, lastPageIndex);
const showLeftEllipsis = leftSiblingIndex > firstPageIndex + 1;
const showRightEllipsis = rightSiblingIndex < lastPageIndex - 1;
const itemCount = totalPageNumbers - 2;
if (!showLeftEllipsis && showRightEllipsis) {
const leftRange = range(1, itemCount);
return [...leftRange, ELLIPSIS, lastPageIndex];
}
if (showLeftEllipsis && !showRightEllipsis) {
const rightRange = range(lastPageIndex - itemCount + 1, lastPageIndex);
return [firstPageIndex, ELLIPSIS, ...rightRange];
}
if (showLeftEllipsis && showRightEllipsis) {
const middleRange = range(leftSiblingIndex, rightSiblingIndex);
return [firstPageIndex, ELLIPSIS, ...middleRange, ELLIPSIS, lastPageIndex];
}
const fullRange = range(firstPageIndex, lastPageIndex);
return fullRange;
};
var getTransformedRange = (ctx) => transform(getRange(ctx));
// src/pagination.connect.ts
function connect(service, normalize) {
const { send, scope, prop, computed, context } = service;
const totalPages = computed("totalPages");
const page = context.get("page");
const translations = prop("translations");
const count = prop("count");
const previousPage = computed("previousPage");
const nextPage = computed("nextPage");
const pageRange = computed("pageRange");
const type = prop("type");
const isButton = type === "button";
const isFirstPage = page === 1;
const isLastPage = page === totalPages;
const pages = getTransformedRange({
page,
totalPages,
siblingCount: prop("siblingCount")
});
return {
count,
page,
pageSize: context.get("pageSize"),
totalPages,
pages,
previousPage,
nextPage,
pageRange,
slice(data) {
return data.slice(pageRange.start, pageRange.end);
},
setPageSize(size) {
send({ type: "SET_PAGE_SIZE", size });
},
setPage(page2) {
send({ type: "SET_PAGE", page: page2 });
},
goToNextPage() {
send({ type: "NEXT_PAGE" });
},
goToPrevPage() {
send({ type: "PREVIOUS_PAGE" });
},
goToFirstPage() {
send({ type: "FIRST_PAGE" });
},
goToLastPage() {
send({ type: "LAST_PAGE" });
},
getRootProps() {
return normalize.element({
id: getRootId(scope),
...parts.root.attrs,
dir: prop("dir"),
"aria-label": translations.rootLabel
});
},
getEllipsisProps(props2) {
return normalize.element({
id: getEllipsisId(scope, props2.index),
...parts.ellipsis.attrs,
dir: prop("dir")
});
},
getItemProps(props2) {
const index = props2.value;
const isCurrentPage = index === page;
return normalize.element({
id: getItemId(scope, index),
...parts.item.attrs,
dir: prop("dir"),
"data-index": index,
"data-selected": domQuery.dataAttr(isCurrentPage),
"aria-current": isCurrentPage ? "page" : void 0,
"aria-label": translations.itemLabel?.({ page: index, totalPages }),
onClick() {
send({ type: "SET_PAGE", page: index });
},
...isButton && { type: "button" }
});
},
getPrevTriggerProps() {
return normalize.element({
id: getPrevTriggerId(scope),
...parts.prevTrigger.attrs,
dir: prop("dir"),
"data-disabled": domQuery.dataAttr(isFirstPage),
"aria-label": translations.prevTriggerLabel,
onClick() {
send({ type: "PREVIOUS_PAGE" });
},
...isButton && { disabled: isFirstPage, type: "button" }
});
},
getNextTriggerProps() {
return normalize.element({
id: getNextTriggerId(scope),
...parts.nextTrigger.attrs,
dir: prop("dir"),
"data-disabled": domQuery.dataAttr(isLastPage),
"aria-label": translations.nextTriggerLabel,
onClick() {
send({ type: "NEXT_PAGE" });
},
...isButton && { disabled: isLastPage, type: "button" }
});
}
};
}
var machine = core.createMachine({
props({ props: props2 }) {
return {
defaultPageSize: 10,
siblingCount: 1,
defaultPage: 1,
type: "button",
count: 1,
...props2,
translations: {
rootLabel: "pagination",
prevTriggerLabel: "previous page",
nextTriggerLabel: "next page",
itemLabel({ page, totalPages }) {
const isLastPage = totalPages > 1 && page === totalPages;
return `${isLastPage ? "last page, " : ""}page ${page}`;
},
...props2.translations
}
};
},
initialState() {
return "idle";
},
context({ prop, bindable, getContext }) {
return {
page: bindable(() => ({
value: prop("page"),
defaultValue: prop("defaultPage"),
onChange(value) {
const context = getContext();
prop("onPageChange")?.({ page: value, pageSize: context.get("pageSize") });
}
})),
pageSize: bindable(() => ({
value: prop("pageSize"),
defaultValue: prop("defaultPageSize"),
onChange(value) {
prop("onPageSizeChange")?.({ pageSize: value });
}
}))
};
},
watch({ track, context, action }) {
track([() => context.get("pageSize")], () => {
action(["setPageIfNeeded"]);
});
},
computed: {
totalPages: ({ context, prop }) => Math.ceil(prop("count") / context.get("pageSize")),
previousPage: ({ context }) => context.get("page") === 1 ? null : context.get("page") - 1,
nextPage: ({ context, computed }) => context.get("page") === computed("totalPages") ? null : context.get("page") + 1,
pageRange: ({ context, prop }) => {
const start = (context.get("page") - 1) * context.get("pageSize");
const end = Math.min(start + context.get("pageSize"), prop("count"));
return { start, end };
},
isValidPage: ({ context, computed }) => context.get("page") >= 1 && context.get("page") <= computed("totalPages")
},
on: {
SET_PAGE: {
guard: "isValidPage",
actions: ["setPage"]
},
SET_PAGE_SIZE: {
actions: ["setPageSize"]
},
FIRST_PAGE: {
actions: ["goToFirstPage"]
},
LAST_PAGE: {
actions: ["goToLastPage"]
},
PREVIOUS_PAGE: {
guard: "canGoToPrevPage",
actions: ["goToPrevPage"]
},
NEXT_PAGE: {
guard: "canGoToNextPage",
actions: ["goToNextPage"]
}
},
states: {
idle: {}
},
implementations: {
guards: {
isValidPage: ({ event, computed }) => event.page >= 1 && event.page <= computed("totalPages"),
isValidCount: ({ context, event }) => context.get("page") > event.count,
canGoToNextPage: ({ context, computed }) => context.get("page") < computed("totalPages"),
canGoToPrevPage: ({ context }) => context.get("page") > 1
},
actions: {
setPage({ context, event, computed }) {
const page = clampPage(event.page, computed("totalPages"));
context.set("page", page);
},
setPageSize({ context, event }) {
context.set("pageSize", event.size);
},
goToFirstPage({ context }) {
context.set("page", 1);
},
goToLastPage({ context, computed }) {
context.set("page", computed("totalPages"));
},
goToPrevPage({ context, computed }) {
context.set("page", (prev) => clampPage(prev - 1, computed("totalPages")));
},
goToNextPage({ context, computed }) {
context.set("page", (prev) => clampPage(prev + 1, computed("totalPages")));
},
setPageIfNeeded({ context, computed }) {
if (computed("isValidPage")) return;
context.set("page", 1);
}
}
}
});
var clampPage = (page, totalPages) => Math.min(Math.max(page, 1), totalPages);
var props = types.createProps()([
"count",
"dir",
"getRootNode",
"id",
"ids",
"onPageChange",
"onPageSizeChange",
"page",
"defaultPage",
"pageSize",
"defaultPageSize",
"siblingCount",
"translations",
"type"
]);
var splitProps = utils.createSplitProps(props);
var itemProps = types.createProps()(["value", "type"]);
var splitItemProps = utils.createSplitProps(itemProps);
var ellipsisProps = types.createProps()(["index"]);
var splitEllipsisProps = utils.createSplitProps(ellipsisProps);
exports.anatomy = anatomy;
exports.connect = connect;
exports.ellipsisProps = ellipsisProps;
exports.itemProps = itemProps;
exports.machine = machine;
exports.props = props;
exports.splitEllipsisProps = splitEllipsisProps;
exports.splitItemProps = splitItemProps;
exports.splitProps = splitProps;