@@ -9,9 +9,43 @@ import {
99 createResource ,
1010 createSignal ,
1111 onCleanup ,
12+ untrack ,
1213} from "solid-js" ;
1314import { isServer } from "solid-js/web" ;
1415
16+ /**
17+ * createSegment - create a reactive segment out of an array of items
18+ * @param {MaybeAccessor<any[]> } items - array of items
19+ * @param {MaybeAccessor<number> } limit - limit of items per segment
20+ * @param {Accessor<number> } page - segment number starting with 1
21+ *
22+ * ```ts
23+ * const [limit, setLimit] = createSignal(10);
24+ * const [paginationProps, page, setPage] = createPagination(() => ({
25+ * pages: Math.ceil(items().length / limit())
26+ * }));
27+ * const segment = createSegment(items, limit, page);
28+ * ```
29+ */
30+ export const createSegment = < T > (
31+ items : MaybeAccessor < T [ ] > ,
32+ limit : MaybeAccessor < number > ,
33+ page : Accessor < number >
34+ ) : Accessor < T [ ] > => {
35+ let previousStart = NaN , previousEnd = NaN ;
36+ return createMemo ( ( previous ) => {
37+ const currentItems = access ( items ) ;
38+ const start = ( page ( ) - 1 ) * access ( limit ) ;
39+ const end = Math . min ( start + access ( limit ) , currentItems . length ) ;
40+ if ( previous && ( previous . length === 0 && end <= start || start === previousStart && end === previousEnd ) ) {
41+ return previous ;
42+ }
43+ previousStart = start ;
44+ previousEnd = end ;
45+ return currentItems . slice ( start , end ) ;
46+ } ) ;
47+ }
48+
1549export type PaginationOptions = {
1650 /** the overall number of pages */
1751 pages : number ;
@@ -35,6 +69,8 @@ export type PaginationOptions = {
3569 nextContent ?: JSX . Element ;
3670 /** content for the last page element, e.g. an SVG icon, default is ">|" */
3771 lastContent ?: JSX . Element ;
72+ /** number of pages a large jump, if it should exist, should skip */
73+ jumpPages ?: number ;
3874} ;
3975
4076export type PaginationProps = {
@@ -98,22 +134,43 @@ export const createPagination = (
98134) : [ props : Accessor < PaginationProps > , page : Accessor < number > , setPage : Setter < number > ] => {
99135 const opts = createMemo ( ( ) => Object . assign ( { } , PAGINATION_DEFAULTS , access ( options ) ) ) ;
100136 const [ page , _setPage ] = createSignal ( opts ( ) . initialPage || 1 ) ;
137+
138+ // do not allow pages beyond the number of pages in case the latter changes
101139 const setPage = ( p : number | ( ( _p : number ) => number ) ) => {
102140 if ( typeof p === "function" ) {
103141 p = p ( page ( ) ) ;
104142 }
105- return p >= 1 && p <= opts ( ) . pages ? _setPage ( p ) : page ( ) ;
143+ if ( p < 1 ) {
144+ return _setPage ( 1 ) ;
145+ }
146+ const pages = opts ( ) . pages ;
147+ if ( p > pages ) {
148+ return _setPage ( pages ) ;
149+ }
150+ return _setPage ( p ) ;
106151 } ;
107152
153+ // normalize in case the number of pages changes, do not run the first time
154+ createComputed ( ( previous ) => {
155+ opts ( ) . pages ;
156+ return previous ? setPage ( untrack ( page ) ) : true ;
157+ } ) ;
158+
159+ const goPage = ( p : number | ( ( p : number ) => number ) , ev : KeyboardEvent ) => {
160+ setPage ( p ) ;
161+ if ( 'currentTarget' in ev )
162+ ( ev . currentTarget as HTMLElement ) . parentNode ?. querySelector < HTMLElement > ( '[aria-current="true"]' ) ?. focus ( ) ;
163+ }
164+
108165 const onKeyUp = ( pageNo : number , ev : KeyboardEvent ) =>
109166 (
110167 ( {
111- ArrowLeft : ( ) => setPage ( p => p - 1 ) ,
112- ArrowRight : ( ) => setPage ( p => p + 1 ) ,
113- Home : ( ) => setPage ( 1 ) ,
114- End : ( ) => setPage ( opts ( ) . pages ) ,
115- Space : ( ) => setPage ( pageNo ) ,
116- Return : ( ) => setPage ( pageNo ) ,
168+ ArrowLeft : ( ) => goPage ( p => p - 1 , ev ) ,
169+ ArrowRight : ( ) => goPage ( p => p + 1 , ev ) ,
170+ Home : ( ) => goPage ( 1 , ev ) ,
171+ End : ( ) => goPage ( opts ( ) . pages , ev ) ,
172+ Space : ( ) => goPage ( pageNo , ev ) ,
173+ Return : ( ) => goPage ( pageNo , ev ) ,
117174 } ) [ ev . key ] || noop
118175 ) ( ) ;
119176
@@ -197,6 +254,32 @@ export const createPagination = (
197254 page : { get : ( ) => opts ( ) . pages , enumerable : false } ,
198255 } ,
199256 ) ;
257+ const jumpBack = Object . defineProperties (
258+ isServer
259+ ? ( { } as PaginationProps [ number ] )
260+ : ( {
261+ onClick : ( ) => setPage ( Math . max ( 1 , page ( ) - ( opts ( ) . jumpPages || Infinity ) ) ) ,
262+ onKeyUp : ( ev : KeyboardEvent ) => onKeyUp ( page ( ) - ( opts ( ) . jumpPages || Infinity ) , ev ) ,
263+ } as unknown as PaginationProps [ number ] ) ,
264+ {
265+ disabled : { get : ( ) => page ( ) - ( opts ( ) . jumpPages || Infinity ) < 0 , set : noop , enumerable : true } ,
266+ children : { get : ( ) => `-${ opts ( ) . jumpPages } ` , set : noop , enumerable : true } ,
267+ page : { get : ( ) => Math . max ( 1 , page ( ) - ( opts ( ) . jumpPages || Infinity ) ) , enumerable : false } ,
268+ }
269+ ) ;
270+ const jumpForth = Object . defineProperties (
271+ isServer
272+ ? ( { } as PaginationProps [ number ] )
273+ : ( {
274+ onClick : ( ) => setPage ( Math . min ( opts ( ) . pages , page ( ) + ( opts ( ) . jumpPages || Infinity ) ) ) ,
275+ onKeyUp : ( ev : KeyboardEvent ) => onKeyUp ( Math . min ( opts ( ) . pages , page ( ) + ( opts ( ) . jumpPages || Infinity ) ) , ev ) ,
276+ } as unknown as PaginationProps [ number ] ) ,
277+ {
278+ disabled : { get : ( ) => page ( ) + ( opts ( ) . jumpPages || Infinity ) > opts ( ) . pages , set : noop , enumerable : true } ,
279+ children : { get : ( ) => `+${ opts ( ) . jumpPages } ` , set : noop , enumerable : true } ,
280+ page : { get : ( ) => Math . min ( opts ( ) . pages , page ( ) + ( opts ( ) . jumpPages || Infinity ) ) , enumerable : false } ,
281+ }
282+ )
200283
201284 const start = createMemo ( ( ) =>
202285 Math . min ( opts ( ) . pages - maxPages ( ) , Math . max ( 1 , page ( ) - ( maxPages ( ) >> 1 ) ) - 1 ) ,
@@ -219,13 +302,19 @@ export const createPagination = (
219302 if ( showFirst ( ) ) {
220303 props . push ( first ) ;
221304 }
305+ if ( opts ( ) . jumpPages ) {
306+ props . push ( jumpBack ) ;
307+ }
222308 if ( showPrev ( ) ) {
223309 props . push ( back ) ;
224310 }
225311 props . push ( ...pages ( ) . slice ( start ( ) , start ( ) + maxPages ( ) ) ) ;
226312 if ( showNext ( ) ) {
227313 props . push ( next ) ;
228314 }
315+ if ( opts ( ) . jumpPages ) {
316+ props . push ( jumpForth ) ;
317+ }
229318 if ( showLast ( ) ) {
230319 props . push ( last ) ;
231320 }
0 commit comments