@rozie-ui/pagination-react 0.0.0-stage → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ import * as _$react from "react";
2
+ import { ReactNode } from "react";
3
+ //#region src/Pagination.d.ts
4
+ /** The `change` event payload: the page that was just navigated to. */
5
+ interface PaginationChangePayload {
6
+ /** The new, clamped, 1-based current page. */
7
+ page: number;
8
+ }
9
+ interface PrevControlCtx {
10
+ disabled: boolean;
11
+ goto: () => void;
12
+ page: number;
13
+ }
14
+ interface EllipsisCtx {
15
+ index: number;
16
+ }
17
+ interface ItemCtx {
18
+ page: number;
19
+ selected: boolean;
20
+ goto: () => void;
21
+ }
22
+ interface NextControlCtx {
23
+ disabled: boolean;
24
+ goto: () => void;
25
+ page: number;
26
+ }
27
+ interface PaginationProps extends Omit<_$react.ComponentPropsWithoutRef<'nav'>, 'modelValue' | 'defaultModelValue' | 'onModelValueChange' | 'totalPages' | 'total' | 'pageSize' | 'siblingCount' | 'boundaryCount' | 'disabled' | 'ariaLabel' | 'onChange' | 'renderPrevControl' | 'renderEllipsis' | 'renderItem' | 'renderNextControl' | 'slots' | 'children' | 'dangerouslySetInnerHTML'> {
28
+ /**
29
+ * The 1-based current page (two-way model). Clamped into `[1, totalPages]`. Bind it with `r-model:modelValue` / `v-model:modelValue` / `modelValue` + `onModelValueChange`; it is also the Angular ControlValueAccessor control value.
30
+ */
31
+ modelValue?: number;
32
+ defaultModelValue?: number;
33
+ onModelValueChange?: (modelValue: number) => void;
34
+ /**
35
+ * Explicit total page count. When provided (> 0) it takes precedence over `total` + `pageSize`. Use it when the backend already reports the page count.
36
+ */
37
+ totalPages?: (number) | null;
38
+ /**
39
+ * Total item count. Combined with `pageSize` to derive the page count (`ceil(total / pageSize)`) when `totalPages` is not given.
40
+ */
41
+ total?: (number) | null;
42
+ /**
43
+ * Items per page. Combined with `total` to derive the page count when `totalPages` is not given.
44
+ */
45
+ pageSize?: (number) | null;
46
+ /**
47
+ * Number of page buttons shown on each side of the current page (the sibling window). Larger values show more context around the current page.
48
+ */
49
+ siblingCount?: number;
50
+ /**
51
+ * Number of page buttons always shown at each boundary (the first and last `boundaryCount` pages), regardless of the current page.
52
+ */
53
+ boundaryCount?: number;
54
+ /**
55
+ * Disable the entire control — every page button and the prev/next controls become non-interactive and are marked `aria-disabled`.
56
+ */
57
+ disabled?: boolean;
58
+ /**
59
+ * Accessible name for the surrounding `<nav>` landmark (its `aria-label`). Defaults to `"Pagination"`.
60
+ */
61
+ ariaLabel?: string;
62
+ onChange?: (payload: PaginationChangePayload) => void;
63
+ renderPrevControl?: (ctx: PrevControlCtx) => ReactNode;
64
+ renderEllipsis?: (ctx: EllipsisCtx) => ReactNode;
65
+ renderItem?: (ctx: ItemCtx) => ReactNode;
66
+ renderNextControl?: (ctx: NextControlCtx) => ReactNode;
67
+ slots?: Record<string, () => _$react.ReactNode>;
68
+ }
69
+ interface PaginationHandle {
70
+ goto: (page: number) => void;
71
+ next: () => void;
72
+ prev: () => void;
73
+ first: () => void;
74
+ last: () => void;
75
+ }
76
+ declare const Pagination: _$react.ForwardRefExoticComponent<PaginationProps & _$react.RefAttributes<PaginationHandle>>;
77
+ //#endregion
78
+ export { Pagination, Pagination as default, PaginationChangePayload, type PaginationHandle };
package/dist/index.mjs ADDED
@@ -0,0 +1,306 @@
1
+ import { Fragment, forwardRef, useCallback, useImperativeHandle, useRef } from "react";
2
+ import { flushSync } from "react-dom";
3
+ import { clsx, mergeListeners, pickListeners, rozieAttr, rozieDisplay, useControllableState } from "@rozie/runtime-react";
4
+ import "./Pagination.css";
5
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/internal/paginationItems.ts
7
+ /** Inclusive integer range [start, end]; empty when start > end. */
8
+ function range(start, end) {
9
+ const out = [];
10
+ for (let i = start; i <= end; i++) out.push(i);
11
+ return out;
12
+ }
13
+ /**
14
+ * Resolve the effective total page count from either an explicit `totalPages`
15
+ * or `ceil(total / pageSize)`. Always at least 1 (an empty data set still has a
16
+ * single page).
17
+ */
18
+ function resolveTotalPages(input) {
19
+ const tp = input.totalPages;
20
+ if (typeof tp === "number" && tp > 0) return Math.max(1, Math.floor(tp));
21
+ const total = typeof input.total === "number" ? input.total : 0;
22
+ const size = typeof input.pageSize === "number" && input.pageSize > 0 ? input.pageSize : 0;
23
+ if (total > 0 && size > 0) return Math.max(1, Math.ceil(total / size));
24
+ return 1;
25
+ }
26
+ /**
27
+ * Build the windowed page-item model. Pure: no clamping side effects, no DOM.
28
+ *
29
+ * Algorithm (MUI usePagination semantics):
30
+ * - `startPages` = first `boundaryCount` pages; `endPages` = last
31
+ * `boundaryCount` pages.
32
+ * - a sibling window of `siblingCount` pages on each side of `page`, kept
33
+ * clear of the boundary blocks.
34
+ * - an `'ellipsis'` collapses a gap of 2+ pages; a gap of exactly one page is
35
+ * rendered as that single bridging page (never a 1-page ellipsis).
36
+ */
37
+ function paginationItems(input) {
38
+ const totalPages = resolveTotalPages(input);
39
+ const rawPage = typeof input.page === "number" && Number.isFinite(input.page) ? Math.floor(input.page) : 1;
40
+ const page = Math.min(Math.max(rawPage, 1), totalPages);
41
+ const siblingCount = Math.max(0, Math.floor(input.siblingCount ?? 1));
42
+ const boundaryCount = Math.max(0, Math.floor(input.boundaryCount ?? 1));
43
+ const startPages = range(1, Math.min(boundaryCount, totalPages));
44
+ const endPages = range(Math.max(totalPages - boundaryCount + 1, boundaryCount + 1), totalPages);
45
+ const siblingsStart = Math.max(Math.min(page - siblingCount, totalPages - boundaryCount - siblingCount * 2 - 1), boundaryCount + 2);
46
+ const endPagesFirst = endPages[0];
47
+ const siblingsEnd = Math.min(Math.max(page + siblingCount, boundaryCount + siblingCount * 2 + 2), endPagesFirst !== void 0 ? endPagesFirst - 2 : totalPages - 1);
48
+ const pages = [];
49
+ pages.push(...startPages);
50
+ if (siblingsStart > boundaryCount + 2) pages.push("ellipsis");
51
+ else if (boundaryCount + 1 < totalPages - boundaryCount) pages.push(boundaryCount + 1);
52
+ pages.push(...range(siblingsStart, siblingsEnd));
53
+ if (siblingsEnd < totalPages - boundaryCount - 1) pages.push("ellipsis");
54
+ else if (totalPages - boundaryCount > boundaryCount) pages.push(totalPages - boundaryCount);
55
+ pages.push(...endPages);
56
+ return {
57
+ totalPages,
58
+ page,
59
+ pages,
60
+ hasPrev: page > 1,
61
+ hasNext: page < totalPages
62
+ };
63
+ }
64
+ //#endregion
65
+ //#region src/Pagination.tsx
66
+ const Pagination = forwardRef(function Pagination(_props, ref) {
67
+ const props = {
68
+ ..._props,
69
+ totalPages: _props.totalPages ?? null,
70
+ total: _props.total ?? null,
71
+ pageSize: _props.pageSize ?? null,
72
+ siblingCount: _props.siblingCount ?? 1,
73
+ boundaryCount: _props.boundaryCount ?? 1,
74
+ disabled: _props.disabled ?? false,
75
+ ariaLabel: _props.ariaLabel ?? "Pagination"
76
+ };
77
+ const attrs = (() => {
78
+ const { modelValue, totalPages, total, pageSize, siblingCount, boundaryCount, disabled, ariaLabel, defaultValue, onModelValueChange, defaultModelValue, onChange, renderPrevControl, renderEllipsis, renderItem, renderNextControl, slots, ...rest } = _props;
79
+ return rest;
80
+ })();
81
+ const [modelValue, setModelValue] = useControllableState({
82
+ value: props.modelValue,
83
+ defaultValue: props.defaultModelValue ?? 1,
84
+ onValueChange: props.onModelValueChange
85
+ });
86
+ const nav = useRef(null);
87
+ function model() {
88
+ return paginationItems({
89
+ page: modelValue,
90
+ totalPages: props.totalPages,
91
+ total: props.total,
92
+ pageSize: props.pageSize,
93
+ siblingCount: props.siblingCount,
94
+ boundaryCount: props.boundaryCount
95
+ });
96
+ }
97
+ function effectivePages() {
98
+ return model().totalPages;
99
+ }
100
+ function currentPage() {
101
+ return model().page;
102
+ }
103
+ function canPrev() {
104
+ return model().hasPrev;
105
+ }
106
+ function canNext() {
107
+ return model().hasNext;
108
+ }
109
+ function isActive(page) {
110
+ return page === currentPage();
111
+ }
112
+ function tabIndexFor(active) {
113
+ return active ? 0 : -1;
114
+ }
115
+ const { onChange: _rozieProp_onChange } = props;
116
+ const goToPage = useCallback((page) => {
117
+ if (props.disabled) return;
118
+ const tp = effectivePages();
119
+ let target = typeof page === "number" ? Math.floor(page) : 1;
120
+ if (target < 1) target = 1;
121
+ if (target > tp) target = tp;
122
+ if (target === currentPage()) return;
123
+ setModelValue(target);
124
+ _rozieProp_onChange && _rozieProp_onChange({ page: target });
125
+ }, [
126
+ _rozieProp_onChange,
127
+ currentPage,
128
+ effectivePages,
129
+ props.disabled,
130
+ setModelValue
131
+ ]);
132
+ const goNext = useCallback(() => {
133
+ if (canNext()) goToPage(currentPage() + 1);
134
+ }, [
135
+ canNext,
136
+ currentPage,
137
+ goToPage
138
+ ]);
139
+ const goPrev = useCallback(() => {
140
+ if (canPrev()) goToPage(currentPage() - 1);
141
+ }, [
142
+ canPrev,
143
+ currentPage,
144
+ goToPage
145
+ ]);
146
+ function goFirst() {
147
+ return goToPage(1);
148
+ }
149
+ function goLast() {
150
+ return goToPage(effectivePages());
151
+ }
152
+ function controls() {
153
+ const nav$local = nav.current;
154
+ if (!nav$local) return [];
155
+ return Array.from(nav$local.querySelectorAll("[data-page-control]"));
156
+ }
157
+ function focusControlAt(idx) {
158
+ const els = controls();
159
+ if (els.length === 0) return;
160
+ let i = idx;
161
+ if (i < 0) i = 0;
162
+ if (i >= els.length) i = els.length - 1;
163
+ const el = els[i];
164
+ if (el && el.focus) el.focus();
165
+ }
166
+ function focusedIndex() {
167
+ const els = controls();
168
+ const nav$local = nav.current;
169
+ const root = nav$local ? nav$local.getRootNode() : null;
170
+ const active = root ? root.activeElement : null;
171
+ return els.indexOf(active);
172
+ }
173
+ const onControlKeydown = useCallback(($event) => {
174
+ if (props.disabled) return;
175
+ const key = $event.key;
176
+ const cur = focusedIndex();
177
+ if (key === "ArrowRight" || key === "ArrowDown") {
178
+ $event.preventDefault();
179
+ focusControlAt(cur + 1);
180
+ } else if (key === "ArrowLeft" || key === "ArrowUp") {
181
+ $event.preventDefault();
182
+ focusControlAt(cur - 1);
183
+ } else if (key === "Home") {
184
+ $event.preventDefault();
185
+ focusControlAt(0);
186
+ } else if (key === "End") {
187
+ $event.preventDefault();
188
+ focusControlAt(controls().length - 1);
189
+ }
190
+ }, [
191
+ controls,
192
+ focusControlAt,
193
+ focusedIndex,
194
+ props.disabled
195
+ ]);
196
+ function goto(page) {
197
+ return goToPage(page);
198
+ }
199
+ function next() {
200
+ return goNext();
201
+ }
202
+ function prev() {
203
+ return goPrev();
204
+ }
205
+ function first() {
206
+ return goFirst();
207
+ }
208
+ function last() {
209
+ return goLast();
210
+ }
211
+ const _rozieExposeRef = useRef({
212
+ goto,
213
+ next,
214
+ prev,
215
+ first,
216
+ last
217
+ });
218
+ _rozieExposeRef.current = {
219
+ goto,
220
+ next,
221
+ prev,
222
+ first,
223
+ last
224
+ };
225
+ useImperativeHandle(ref, () => ({
226
+ goto: (...args) => flushSync(() => _rozieExposeRef.current.goto(...args)),
227
+ next: (...args) => flushSync(() => _rozieExposeRef.current.next(...args)),
228
+ prev: (...args) => flushSync(() => _rozieExposeRef.current.prev(...args)),
229
+ first: (...args) => flushSync(() => _rozieExposeRef.current.first(...args)),
230
+ last: (...args) => flushSync(() => _rozieExposeRef.current.last(...args))
231
+ }), []);
232
+ return /* @__PURE__ */ jsx(Fragment$1, { children: /* @__PURE__ */ jsxs("nav", {
233
+ ref: nav,
234
+ "aria-label": props.ariaLabel,
235
+ ...attrs,
236
+ className: clsx(clsx("rozie-pagination", { "rozie-pagination--disabled": props.disabled }), attrs.className),
237
+ ...mergeListeners({ onKeyDown: ($event) => {
238
+ onControlKeydown($event);
239
+ } }, pickListeners(attrs)),
240
+ "data-rozie-s-de247ae2": "",
241
+ children: [
242
+ props.renderPrevControl ?? props.slots?.["prevControl"] ? (props.renderPrevControl ?? props.slots?.["prevControl"])({
243
+ disabled: !canPrev() || props.disabled,
244
+ goto: goPrev,
245
+ page: currentPage() - 1
246
+ }) : /* @__PURE__ */ jsx("button", {
247
+ type: "button",
248
+ className: "rozie-pagination-control rozie-pagination-prev",
249
+ "data-page-control": "",
250
+ tabIndex: tabIndexFor(true),
251
+ disabled: !canPrev() || props.disabled,
252
+ "aria-disabled": !!(!canPrev() || props.disabled),
253
+ "aria-label": "Previous page",
254
+ onClick: goPrev,
255
+ "data-rozie-s-de247ae2": "",
256
+ children: "‹"
257
+ }),
258
+ model().pages.map((item, index) => /* @__PURE__ */ jsxs(Fragment, { children: [!!(item === "ellipsis") && /* @__PURE__ */ jsx("span", {
259
+ className: "rozie-pagination-ellipsis",
260
+ "aria-hidden": "true",
261
+ "data-rozie-s-de247ae2": "",
262
+ children: props.renderEllipsis ?? props.slots?.["ellipsis"] ? (props.renderEllipsis ?? props.slots?.["ellipsis"])({ index }) : "…"
263
+ }), !!(item !== "ellipsis") && /* @__PURE__ */ jsx("span", {
264
+ className: "rozie-pagination-item",
265
+ "data-rozie-s-de247ae2": "",
266
+ children: props.renderItem ?? props.slots?.["item"] ? (props.renderItem ?? props.slots?.["item"])({
267
+ page: item,
268
+ selected: isActive(item),
269
+ goto: () => goToPage(item)
270
+ }) : /* @__PURE__ */ jsx("button", {
271
+ type: "button",
272
+ className: clsx("rozie-pagination-page", { "is-active": isActive(item) }),
273
+ "data-page-control": "",
274
+ tabIndex: tabIndexFor(isActive(item)),
275
+ disabled: !!props.disabled,
276
+ "aria-disabled": !!props.disabled,
277
+ "aria-current": rozieAttr(isActive(item) ? "page" : void 0),
278
+ "aria-label": rozieAttr("Go to page " + item),
279
+ onClick: ($event) => {
280
+ goToPage(item);
281
+ },
282
+ "data-rozie-s-de247ae2": "",
283
+ children: rozieDisplay(item)
284
+ })
285
+ })] }, item === "ellipsis" ? "ellipsis-" + index : item)),
286
+ props.renderNextControl ?? props.slots?.["nextControl"] ? (props.renderNextControl ?? props.slots?.["nextControl"])({
287
+ disabled: !canNext() || props.disabled,
288
+ goto: goNext,
289
+ page: currentPage() + 1
290
+ }) : /* @__PURE__ */ jsx("button", {
291
+ type: "button",
292
+ className: "rozie-pagination-control rozie-pagination-next",
293
+ "data-page-control": "",
294
+ tabIndex: tabIndexFor(true),
295
+ disabled: !canNext() || props.disabled,
296
+ "aria-disabled": !!(!canNext() || props.disabled),
297
+ "aria-label": "Next page",
298
+ onClick: goNext,
299
+ "data-rozie-s-de247ae2": "",
300
+ children: "›"
301
+ })
302
+ ]
303
+ }) });
304
+ });
305
+ //#endregion
306
+ export { Pagination, Pagination as default };
package/package.json CHANGED
@@ -1,6 +1,74 @@
1
1
  {
2
2
  "name": "@rozie-ui/pagination-react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.8",
4
+ "type": "module",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "description": "Idiomatic React headless WAI-ARIA pagination control (page-item model with ellipses, sibling/boundary windowing, prev/next, roving keyboard nav) — one accessible Rozie source compiled to React.",
8
+ "keywords": [
9
+ "rozie",
10
+ "rozie-ui",
11
+ "pagination",
12
+ "pager",
13
+ "page-navigation",
14
+ "nav",
15
+ "headless",
16
+ "accessibility",
17
+ "aria",
18
+ "component",
19
+ "react"
20
+ ],
21
+ "author": "One Learning Community (https://github.com/One-Learning-Community)",
22
+ "homepage": "https://github.com/One-Learning-Community/rozie.js#readme",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/One-Learning-Community/rozie.js.git",
26
+ "directory": "packages/ui/pagination/packages/react"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/One-Learning-Community/rozie.js/issues"
30
+ },
31
+ "main": "./dist/index.cjs",
32
+ "module": "./dist/index.mjs",
33
+ "types": "./dist/index.d.mts",
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.mts",
37
+ "import": "./dist/index.mjs",
38
+ "require": "./dist/index.cjs"
39
+ },
40
+ "./source": "./src/Pagination.tsx",
41
+ "./themes/*": "./src/themes/*"
42
+ },
43
+ "files": [
44
+ "dist",
45
+ "src",
46
+ "CHANGELOG.md"
47
+ ],
48
+ "dependencies": {
49
+ "@rozie/runtime-react": "^0.10.1"
50
+ },
51
+ "peerDependencies": {
52
+ "react": "^18.2 || ^19",
53
+ "react-dom": "^18.2 || ^19"
54
+ },
55
+ "peerDependenciesMeta": {
56
+ "react": {
57
+ "optional": false
58
+ },
59
+ "react-dom": {
60
+ "optional": false
61
+ }
62
+ },
63
+ "devDependencies": {
64
+ "@types/react": "^18",
65
+ "@types/react-dom": "^18"
66
+ },
67
+ "publishConfig": {
68
+ "access": "public"
69
+ },
70
+ "scripts": {
71
+ "build": "tsdown",
72
+ "typecheck": "tsc --noEmit"
73
+ }
6
74
  }
@@ -0,0 +1,59 @@
1
+ .rozie-pagination[data-rozie-s-de247ae2] {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--rozie-pagination-gap, var(--rpg-gap, 0.25rem));
5
+ font: var(--rozie-pagination-font, inherit);
6
+ }
7
+ .rozie-pagination-page[data-rozie-s-de247ae2],
8
+ .rozie-pagination-control[data-rozie-s-de247ae2] {
9
+ box-sizing: border-box;
10
+ min-width: var(--rozie-pagination-size, var(--rpg-size, 2.25rem));
11
+ height: var(--rozie-pagination-size, var(--rpg-size, 2.25rem));
12
+ padding: 0 var(--rozie-pagination-padding-x, var(--rpg-padding-x, 0.5rem));
13
+ display: inline-flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+ font: inherit;
17
+ font-weight: var(--rozie-pagination-font-weight, var(--rpg-font-weight, 500));
18
+ color: var(--rozie-pagination-fg, var(--rpg-fg, #1a1a1a));
19
+ background: var(--rozie-pagination-bg, var(--rpg-bg, transparent));
20
+ border: var(--rozie-pagination-border-width, var(--rpg-border-width, 1px)) solid var(--rozie-pagination-border, var(--rpg-border, rgba(0, 0, 0, 0.18)));
21
+ border-radius: var(--rozie-pagination-radius, var(--rpg-radius, 6px));
22
+ cursor: pointer;
23
+ user-select: none;
24
+ transition: background 0.12s, border-color 0.12s, color 0.12s;
25
+ }
26
+ .rozie-pagination-page[data-rozie-s-de247ae2]:hover,
27
+ .rozie-pagination-control[data-rozie-s-de247ae2]:hover {
28
+ background: var(--rozie-pagination-hover-bg, var(--rpg-hover-bg, rgba(0, 0, 0, 0.05)));
29
+ border-color: var(--rozie-pagination-hover-border, var(--rpg-hover-border, rgba(0, 0, 0, 0.28)));
30
+ }
31
+ .rozie-pagination-page[data-rozie-s-de247ae2]:focus-visible,
32
+ .rozie-pagination-control[data-rozie-s-de247ae2]:focus-visible {
33
+ outline: var(--rozie-pagination-ring-width, var(--rpg-ring-width, 2px)) solid var(--rozie-pagination-ring, var(--rozie-pagination-accent, var(--rpg-ring, var(--rpg-accent, #0066cc))));
34
+ outline-offset: var(--rozie-pagination-ring-offset, var(--rpg-ring-offset, 1px));
35
+ }
36
+ .rozie-pagination-page.is-active[data-rozie-s-de247ae2] {
37
+ color: var(--rozie-pagination-active-fg, var(--rpg-active-fg, #fff));
38
+ background: var(--rozie-pagination-active-bg, var(--rozie-pagination-accent, var(--rpg-active-bg, var(--rpg-accent, #0066cc))));
39
+ border-color: var(--rozie-pagination-active-border, var(--rozie-pagination-accent, var(--rpg-active-border, var(--rpg-accent, #0066cc))));
40
+ }
41
+ .rozie-pagination-page[data-rozie-s-de247ae2]:disabled,
42
+ .rozie-pagination-control[data-rozie-s-de247ae2]:disabled {
43
+ cursor: not-allowed;
44
+ opacity: var(--rozie-pagination-disabled-opacity, var(--rpg-disabled-opacity, 0.5));
45
+ pointer-events: none;
46
+ }
47
+ .rozie-pagination--disabled[data-rozie-s-de247ae2] {
48
+ opacity: var(--rozie-pagination-disabled-opacity, var(--rpg-disabled-opacity, 0.5));
49
+ pointer-events: none;
50
+ }
51
+ .rozie-pagination-ellipsis[data-rozie-s-de247ae2] {
52
+ min-width: var(--rozie-pagination-size, var(--rpg-size, 2.25rem));
53
+ height: var(--rozie-pagination-size, var(--rpg-size, 2.25rem));
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ color: var(--rozie-pagination-ellipsis-fg, var(--rpg-ellipsis-fg, rgba(0, 0, 0, 0.5)));
58
+ user-select: none;
59
+ }
@@ -0,0 +1,63 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { ForwardRefExoticComponent, RefAttributes } from 'react';
3
+ import type * as React from 'react';
4
+
5
+ /** The `change` event payload: the page that was just navigated to. */
6
+ export interface PaginationChangePayload {
7
+ /** The new, clamped, 1-based current page. */
8
+ page: number;
9
+ }
10
+
11
+ export interface PaginationProps extends Omit<import('react').ComponentPropsWithoutRef<'nav'>, 'modelValue' | 'defaultModelValue' | 'onModelValueChange' | 'totalPages' | 'total' | 'pageSize' | 'siblingCount' | 'boundaryCount' | 'disabled' | 'ariaLabel' | 'onChange' | 'renderPrevControl' | 'renderEllipsis' | 'renderItem' | 'renderNextControl' | 'slots' | 'children' | 'dangerouslySetInnerHTML'> {
12
+ /**
13
+ * The 1-based current page (two-way model). Clamped into `[1, totalPages]`. Bind it with `r-model:modelValue` / `v-model:modelValue` / `modelValue` + `onModelValueChange`; it is also the Angular ControlValueAccessor control value.
14
+ */
15
+ modelValue?: number;
16
+ defaultModelValue?: number;
17
+ onModelValueChange?: (next: number) => void;
18
+ /**
19
+ * Explicit total page count. When provided (> 0) it takes precedence over `total` + `pageSize`. Use it when the backend already reports the page count.
20
+ */
21
+ totalPages?: (number) | null;
22
+ /**
23
+ * Total item count. Combined with `pageSize` to derive the page count (`ceil(total / pageSize)`) when `totalPages` is not given.
24
+ */
25
+ total?: (number) | null;
26
+ /**
27
+ * Items per page. Combined with `total` to derive the page count when `totalPages` is not given.
28
+ */
29
+ pageSize?: (number) | null;
30
+ /**
31
+ * Number of page buttons shown on each side of the current page (the sibling window). Larger values show more context around the current page.
32
+ */
33
+ siblingCount?: number;
34
+ /**
35
+ * Number of page buttons always shown at each boundary (the first and last `boundaryCount` pages), regardless of the current page.
36
+ */
37
+ boundaryCount?: number;
38
+ /**
39
+ * Disable the entire control — every page button and the prev/next controls become non-interactive and are marked `aria-disabled`.
40
+ */
41
+ disabled?: boolean;
42
+ /**
43
+ * Accessible name for the surrounding `<nav>` landmark (its `aria-label`). Defaults to `"Pagination"`.
44
+ */
45
+ ariaLabel?: string;
46
+ onChange?: (payload: PaginationChangePayload) => void;
47
+ renderPrevControl?: (params: { disabled: boolean; goto: () => void; page: number }) => ReactNode;
48
+ renderEllipsis?: (params: { index: number }) => ReactNode;
49
+ renderItem?: (params: { page: number; selected: boolean; goto: () => void }) => ReactNode;
50
+ renderNextControl?: (params: { disabled: boolean; goto: () => void; page: number }) => ReactNode;
51
+ slots?: Record<string, () => ReactNode>;
52
+ }
53
+
54
+ export interface PaginationHandle {
55
+ goto: (page: number) => void;
56
+ next: () => void;
57
+ prev: () => void;
58
+ first: () => void;
59
+ last: () => void;
60
+ }
61
+
62
+ declare const Pagination: React.ForwardRefExoticComponent<PaginationProps & React.RefAttributes<PaginationHandle>>;
63
+ export default Pagination;