Skip to content

Commit 680b322

Browse files
authored
Merge pull request #60 from techniq/add-paginationstate
Add PaginationState
2 parents 40bbe6d + 295ab24 commit 680b322

File tree

6 files changed

+290
-1
lines changed

6 files changed

+290
-1
lines changed

.changeset/odd-dots-greet.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@layerstack/svelte-state': patch
3+
---
4+
5+
Add PaginationState
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { clamp } from '@layerstack/utils';
2+
3+
export type PaginationOptions = {
4+
/** Initial page */
5+
page?: number;
6+
7+
/** Number of items per page */
8+
perPage?: number;
9+
10+
/** Total number of items */
11+
total?: number;
12+
};
13+
14+
export class PaginationState {
15+
#page: number;
16+
#perPage: number;
17+
#total: number;
18+
19+
constructor(options: PaginationOptions = {}) {
20+
this.#page = options.page ?? 1;
21+
this.#perPage = options.perPage ?? 25;
22+
this.#total = options.total ?? 0;
23+
}
24+
25+
get page() {
26+
return this.#page;
27+
}
28+
29+
set page(value: number) {
30+
// Do not allow page to exceed bounds (ex. call nextPage() when on last page)
31+
this.#page = clamp(value, 1, this.totalPages);
32+
}
33+
34+
get perPage() {
35+
return this.#perPage;
36+
}
37+
38+
set perPage(value: number) {
39+
this.#perPage = value;
40+
}
41+
42+
get total() {
43+
return this.#total;
44+
}
45+
46+
set total(value: number) {
47+
this.#total = value;
48+
}
49+
50+
get totalPages() {
51+
return Math.ceil(this.total / this.perPage);
52+
}
53+
54+
get from() {
55+
return Math.min(this.total, Math.max(0, (this.page - 1) * this.perPage + 1));
56+
}
57+
58+
get to() {
59+
return Math.min(this.total, this.page * this.perPage);
60+
}
61+
62+
get isFirst() {
63+
return this.page === 1;
64+
}
65+
66+
get isLast() {
67+
return this.page >= this.totalPages;
68+
}
69+
70+
get hasPrevious() {
71+
return this.page > 1 && this.totalPages > 0;
72+
}
73+
74+
get hasNext() {
75+
return this.page < this.totalPages;
76+
}
77+
78+
nextPage = () => {
79+
this.page = this.page + 1;
80+
};
81+
82+
prevPage = () => {
83+
this.page = this.page - 1;
84+
};
85+
86+
firstPage = () => {
87+
this.page = 1;
88+
};
89+
90+
lastPage = () => {
91+
this.page = Math.ceil(this.total / this.perPage);
92+
};
93+
94+
slice<T>(data: T[]) {
95+
return data.slice((this.page - 1) * this.perPage, this.page * this.perPage);
96+
}
97+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
import { PaginationState } from './paginationState.svelte.js';
4+
5+
describe('PaginationState', () => {
6+
it('should initialize with default values', () => {
7+
const paginationState = new PaginationState();
8+
expect(paginationState.page).toEqual(1);
9+
expect(paginationState.perPage).toEqual(25);
10+
expect(paginationState.total).toEqual(0);
11+
expect(paginationState.totalPages).toEqual(0);
12+
expect(paginationState.from).toEqual(0);
13+
expect(paginationState.to).toEqual(0);
14+
expect(paginationState.isFirst).toEqual(true);
15+
expect(paginationState.isLast).toEqual(true);
16+
expect(paginationState.hasPrevious).toEqual(false);
17+
expect(paginationState.hasNext).toEqual(false);
18+
});
19+
20+
it('should initialize with total only', () => {
21+
const paginationState = new PaginationState({ total: 100 });
22+
expect(paginationState.page).toEqual(1);
23+
expect(paginationState.perPage).toEqual(25);
24+
expect(paginationState.total).toEqual(100);
25+
expect(paginationState.totalPages).toEqual(4);
26+
expect(paginationState.from).toEqual(1);
27+
expect(paginationState.to).toEqual(25);
28+
expect(paginationState.isFirst).toEqual(true);
29+
expect(paginationState.isLast).toEqual(false);
30+
expect(paginationState.hasPrevious).toEqual(false);
31+
expect(paginationState.hasNext).toEqual(true);
32+
});
33+
34+
it('should initialize with page', () => {
35+
const paginationState = new PaginationState({ page: 2, total: 100 });
36+
expect(paginationState.page).toEqual(2);
37+
expect(paginationState.perPage).toEqual(25);
38+
expect(paginationState.total).toEqual(100);
39+
expect(paginationState.totalPages).toEqual(4);
40+
expect(paginationState.from).toEqual(26);
41+
expect(paginationState.to).toEqual(50);
42+
expect(paginationState.isFirst).toEqual(false);
43+
expect(paginationState.isLast).toEqual(false);
44+
expect(paginationState.hasPrevious).toEqual(true);
45+
expect(paginationState.hasNext).toEqual(true);
46+
});
47+
48+
it('should initialize with perPage', () => {
49+
const paginationState = new PaginationState({ perPage: 10, total: 100 });
50+
expect(paginationState.page).toEqual(1);
51+
expect(paginationState.perPage).toEqual(10);
52+
expect(paginationState.total).toEqual(100);
53+
expect(paginationState.totalPages).toEqual(10);
54+
expect(paginationState.from).toEqual(1);
55+
expect(paginationState.to).toEqual(10);
56+
expect(paginationState.isFirst).toEqual(true);
57+
expect(paginationState.isLast).toEqual(false);
58+
expect(paginationState.hasPrevious).toEqual(false);
59+
expect(paginationState.hasNext).toEqual(true);
60+
});
61+
62+
it('should increment page', () => {
63+
const paginationState = new PaginationState({ total: 100 });
64+
expect(paginationState.page).toEqual(1);
65+
expect(paginationState.from).toEqual(1);
66+
expect(paginationState.to).toEqual(25);
67+
expect(paginationState.isFirst).toEqual(true);
68+
expect(paginationState.isLast).toEqual(false);
69+
expect(paginationState.hasPrevious).toEqual(false);
70+
expect(paginationState.hasNext).toEqual(true);
71+
72+
paginationState.nextPage();
73+
expect(paginationState.page).toEqual(2);
74+
expect(paginationState.from).toEqual(26);
75+
expect(paginationState.to).toEqual(50);
76+
expect(paginationState.isFirst).toEqual(false);
77+
expect(paginationState.isLast).toEqual(false);
78+
expect(paginationState.hasPrevious).toEqual(true);
79+
expect(paginationState.hasNext).toEqual(true);
80+
});
81+
82+
it('should decrement page', () => {
83+
const paginationState = new PaginationState({ page: 2, total: 100 });
84+
expect(paginationState.page).toEqual(2);
85+
expect(paginationState.from).toEqual(26);
86+
expect(paginationState.to).toEqual(50);
87+
expect(paginationState.isFirst).toEqual(false);
88+
expect(paginationState.isLast).toEqual(false);
89+
expect(paginationState.hasPrevious).toEqual(true);
90+
expect(paginationState.hasNext).toEqual(true);
91+
92+
paginationState.prevPage();
93+
expect(paginationState.page).toEqual(1);
94+
expect(paginationState.from).toEqual(1);
95+
expect(paginationState.to).toEqual(25);
96+
expect(paginationState.isFirst).toEqual(true);
97+
expect(paginationState.isLast).toEqual(false);
98+
expect(paginationState.hasPrevious).toEqual(false);
99+
expect(paginationState.hasNext).toEqual(true);
100+
});
101+
102+
it('should clamp page', () => {
103+
const paginationState = new PaginationState({ page: 4, total: 100 });
104+
expect(paginationState.page).toEqual(4);
105+
expect(paginationState.from).toEqual(76);
106+
expect(paginationState.to).toEqual(100);
107+
expect(paginationState.isFirst).toEqual(false);
108+
expect(paginationState.isLast).toEqual(true);
109+
expect(paginationState.hasPrevious).toEqual(true);
110+
expect(paginationState.hasNext).toEqual(false);
111+
112+
paginationState.nextPage();
113+
expect(paginationState.page).toEqual(4);
114+
expect(paginationState.from).toEqual(76);
115+
expect(paginationState.to).toEqual(100);
116+
expect(paginationState.isFirst).toEqual(false);
117+
expect(paginationState.isLast).toEqual(true);
118+
expect(paginationState.hasPrevious).toEqual(true);
119+
expect(paginationState.hasNext).toEqual(false);
120+
});
121+
122+
it('should slice data', () => {
123+
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
124+
const paginationState = new PaginationState({ perPage: 5, total: data.length });
125+
expect(paginationState.slice(data)).toEqual([1, 2, 3, 4, 5]);
126+
127+
paginationState.nextPage();
128+
expect(paginationState.slice(data)).toEqual([6, 7, 8, 9, 10]);
129+
130+
paginationState.nextPage();
131+
expect(paginationState.slice(data)).toEqual([6, 7, 8, 9, 10]); // clamped
132+
133+
paginationState.prevPage();
134+
expect(paginationState.slice(data)).toEqual([1, 2, 3, 4, 5]);
135+
136+
paginationState.prevPage();
137+
expect(paginationState.slice(data)).toEqual([1, 2, 3, 4, 5]); // clamped
138+
});
139+
});

sites/docs/src/routes/_NavMenu.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,13 @@
1919
'styles',
2020
];
2121
22-
const state = ['MediaQueryPresets', 'SelectionState', 'TimerState', 'UniqueState'];
22+
const state = [
23+
'MediaQueryPresets',
24+
'PaginationState',
25+
'SelectionState',
26+
'TimerState',
27+
'UniqueState',
28+
];
2329
2430
const stores = [
2531
'changeStore',
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script lang="ts">
2+
import Code from '$docs/Code.svelte';
3+
import Preview from '$docs/Preview.svelte';
4+
5+
import { PaginationState } from '$svelte-state/paginationState.svelte.js';
6+
</script>
7+
8+
<h1>Usage</h1>
9+
10+
<Code
11+
source={`import { PaginationState } from '@layerstack/svelte-state';
12+
13+
const state = new PaginationState({ total: 100 });
14+
15+
state.page
16+
state.perPage
17+
state.total
18+
state.totalPages
19+
state.from
20+
state.to
21+
state.isFirst
22+
state.isLast
23+
state.hasPrevious
24+
state.hasNext
25+
state.slice(data)
26+
`}
27+
language="javascript"
28+
/>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import source from '$svelte-state/paginationState.svelte.js?raw';
2+
import pageSource from './+page.svelte?raw';
3+
4+
export async function load() {
5+
return {
6+
meta: {
7+
source,
8+
pageSource,
9+
description:
10+
'Manage pagination state including current page and page navigation (next/previous/first/last). See related Paginate/Pagination components',
11+
related: ['components/Paginate', 'components/Pagination'],
12+
},
13+
};
14+
}

0 commit comments

Comments
 (0)