@boxcustodia/library 2.0.0-alpha.31 → 2.0.0-alpha.32

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.
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react"),f=require("../internal/use-latest-ref.cjs.js"),G=10,O=1;function m(e,r){return e<=0||Number.isNaN(e)?(process.env.NODE_ENV!=="production"&&(!r||!r.current)&&(r&&(r.current=!0),console.warn(`[usePagination] pageSize must be a positive number, received ${e}. Coercing to 1.`)),1):Math.floor(e)}function U(e,r){return e<=0?0:Math.ceil(e/r)}function d(e,r){return r<=0||Number.isNaN(e)?1:Math.min(Math.max(1,Math.floor(e)),r)}function j({totalItems:e,pageSize:r,defaultPageSize:z=G,onPageSizeChange:b,page:C,defaultPage:A=O,onPageChange:T,onPaginationChange:_}){const[p,R]=n.useState(A),[S,h]=n.useState(z),x=n.useRef(!1),c=m(r??S,x),o=U(e,c),u=d(C??p,o),i=f.useLatestRef(u),y=f.useLatestRef(c),s=f.useLatestRef(o),L=f.useLatestRef(T),N=f.useLatestRef(b),D=f.useLatestRef(_),P=n.useRef(!1),l=n.useRef(null),E=n.useRef(null);n.useEffect(()=>{if(!P.current)return;const t=d(p,s.current);l.current!==t&&(l.current=t,L.current?.(t))},[p]),n.useEffect(()=>{if(!P.current)return;const t=m(S);E.current!==t&&(E.current=t,N.current?.(t))},[S]),n.useEffect(()=>{P.current&&D.current?.({page:u,pageSize:c})},[u,c]),n.useEffect(()=>{P.current=!0},[]);const F=n.useMemo(()=>{const t=g=>{const a=d(g,s.current);a!==i.current&&R(a)};return{goTo:g=>{s.current<=0||t(g)},next:()=>{i.current>=s.current||t(i.current+1)},prev:()=>{i.current<=1||t(i.current-1)},firstPage:()=>{s.current<=0||t(1)},lastPage:()=>{s.current<=0||t(s.current)},setPageSize:g=>{const a=m(g);a!==y.current&&(E.current=a,h(a),N.current?.(a),i.current!==1&&(l.current=1,L.current?.(1)),R(1))}}},[R,h]),v=o<=0?!0:u<=1,M=o<=0?!0:u>=o,q=e<=0?{start:0,end:0}:{start:(u-1)*c+1,end:Math.min(u*c,e)};return{page:u,pageSize:c,pageCount:o,isFirstPage:v,isLastPage:M,hasPrevPage:!v,hasNextPage:!M,range:q,...F}}exports.usePagination=j;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("react"),o=require("../internal/use-latest-ref.cjs.js"),O=10,U=1;function d(e,r){return e<=0||Number.isNaN(e)?(process.env.NODE_ENV!=="production"&&(!r||!r.current)&&(r&&(r.current=!0),console.warn(`[usePagination] pageSize must be a positive number, received ${e}. Coercing to 1.`)),1):Math.floor(e)}function j(e,r){return e<=0?0:Math.ceil(e/r)}function h(e,r){return r<=0||Number.isNaN(e)?1:Math.min(Math.max(1,Math.floor(e)),r)}function V({totalItems:e,pageSize:r,defaultPageSize:z=O,onPageSizeChange:b,page:C,defaultPage:A=U,onPageChange:T,onPaginationChange:_}){const[P,S]=n.useState(A),[l,L]=n.useState(z),x=n.useRef(!1),s=d(r??l,x),i=j(e,s),c=h(C??P,i),f=o.useLatestRef(c),y=o.useLatestRef(P),D=o.useLatestRef(s),a=o.useLatestRef(i),E=o.useLatestRef(T),N=o.useLatestRef(b),F=o.useLatestRef(_),R=n.useRef(!1),p=n.useRef(null),m=n.useRef(null);n.useEffect(()=>{if(!R.current)return;const t=h(P,a.current);p.current!==t&&(p.current=t,E.current?.(t))},[P]),n.useEffect(()=>{if(!R.current)return;const t=d(l);m.current!==t&&(m.current=t,N.current?.(t))},[l]),n.useEffect(()=>{R.current&&F.current?.({page:c,pageSize:s})},[c,s]),n.useEffect(()=>{R.current=!0},[]);const q=n.useMemo(()=>{const t=g=>{const u=h(g,a.current);u!==f.current&&(y.current===u&&(p.current=u,E.current?.(u)),S(u))};return{goTo:g=>{a.current<=0||t(g)},next:()=>{f.current>=a.current||t(f.current+1)},prev:()=>{f.current<=1||t(f.current-1)},firstPage:()=>{a.current<=0||t(1)},lastPage:()=>{a.current<=0||t(a.current)},setPageSize:g=>{const u=d(g);u!==D.current&&(m.current=u,L(u),N.current?.(u),f.current!==1&&(p.current=1,E.current?.(1)),S(1))}}},[S,L]),v=i<=0?!0:c<=1,M=i<=0?!0:c>=i,G=e<=0?{start:0,end:0}:{start:(c-1)*s+1,end:Math.min(c*s,e)};return{page:c,pageSize:s,pageCount:i,isFirstPage:v,isLastPage:M,hasPrevPage:!v,hasNextPage:!M,range:G,...q}}exports.usePagination=V;
@@ -1,83 +1,83 @@
1
- import { useState as b, useRef as P, useEffect as p, useMemo as V } from "react";
2
- import { useLatestRef as s } from "../internal/use-latest-ref.es.js";
3
- const Z = 10, $ = 1;
1
+ import { useState as b, useRef as S, useEffect as l, useMemo as Z } from "react";
2
+ import { useLatestRef as o } from "../internal/use-latest-ref.es.js";
3
+ const $ = 10, j = 1;
4
4
  function d(e, t) {
5
5
  return e <= 0 || Number.isNaN(e) ? (process.env.NODE_ENV !== "production" && (!t || !t.current) && (t && (t.current = !0), console.warn(
6
6
  `[usePagination] pageSize must be a positive number, received ${e}. Coercing to 1.`
7
7
  )), 1) : Math.floor(e);
8
8
  }
9
- function j(e, t) {
9
+ function k(e, t) {
10
10
  return e <= 0 ? 0 : Math.ceil(e / t);
11
11
  }
12
12
  function N(e, t) {
13
13
  return t <= 0 || Number.isNaN(e) ? 1 : Math.min(Math.max(1, Math.floor(e)), t);
14
14
  }
15
- function B({
15
+ function H({
16
16
  totalItems: e,
17
17
  pageSize: t,
18
- defaultPageSize: x = Z,
18
+ defaultPageSize: x = $,
19
19
  onPageSizeChange: A,
20
20
  page: L,
21
- defaultPage: _ = $,
21
+ defaultPage: _ = j,
22
22
  onPageChange: D,
23
23
  onPaginationChange: F
24
24
  }) {
25
- const [S, l] = b(_), [m, R] = b(x), T = P(!1), u = d(t ?? m, T), o = j(e, u), n = N(L ?? S, o), i = s(n), G = s(u), c = s(o), v = s(D), z = s(A), U = s(F), g = P(!1), h = P(null), E = P(null);
26
- p(() => {
27
- if (!g.current) return;
28
- const r = N(S, c.current);
29
- h.current !== r && (h.current = r, v.current?.(r));
30
- }, [S]), p(() => {
31
- if (!g.current) return;
32
- const r = d(m);
33
- E.current !== r && (E.current = r, z.current?.(r));
34
- }, [m]), p(() => {
35
- g.current && U.current?.({ page: n, pageSize: u });
36
- }, [n, u]), p(() => {
37
- g.current = !0;
25
+ const [g, m] = b(_), [h, v] = b(x), T = S(!1), c = d(t ?? h, T), i = k(e, c), u = N(L ?? g, i), s = o(u), G = o(g), U = o(c), a = o(i), E = o(D), z = o(A), y = o(F), P = S(!1), p = S(null), R = S(null);
26
+ l(() => {
27
+ if (!P.current) return;
28
+ const r = N(g, a.current);
29
+ p.current !== r && (p.current = r, E.current?.(r));
30
+ }, [g]), l(() => {
31
+ if (!P.current) return;
32
+ const r = d(h);
33
+ R.current !== r && (R.current = r, z.current?.(r));
34
+ }, [h]), l(() => {
35
+ P.current && y.current?.({ page: u, pageSize: c });
36
+ }, [u, c]), l(() => {
37
+ P.current = !0;
38
38
  }, []);
39
- const y = V(() => {
39
+ const O = Z(() => {
40
40
  const r = (f) => {
41
- const a = N(f, c.current);
42
- a !== i.current && l(a);
41
+ const n = N(f, a.current);
42
+ n !== s.current && (G.current === n && (p.current = n, E.current?.(n)), m(n));
43
43
  };
44
44
  return {
45
45
  goTo: (f) => {
46
- c.current <= 0 || r(f);
46
+ a.current <= 0 || r(f);
47
47
  },
48
48
  next: () => {
49
- i.current >= c.current || r(i.current + 1);
49
+ s.current >= a.current || r(s.current + 1);
50
50
  },
51
51
  prev: () => {
52
- i.current <= 1 || r(i.current - 1);
52
+ s.current <= 1 || r(s.current - 1);
53
53
  },
54
54
  firstPage: () => {
55
- c.current <= 0 || r(1);
55
+ a.current <= 0 || r(1);
56
56
  },
57
57
  lastPage: () => {
58
- c.current <= 0 || r(c.current);
58
+ a.current <= 0 || r(a.current);
59
59
  },
60
60
  setPageSize: (f) => {
61
- const a = d(f);
62
- a !== G.current && (E.current = a, R(a), z.current?.(a), i.current !== 1 && (h.current = 1, v.current?.(1)), l(1));
61
+ const n = d(f);
62
+ n !== U.current && (R.current = n, v(n), z.current?.(n), s.current !== 1 && (p.current = 1, E.current?.(1)), m(1));
63
63
  }
64
64
  };
65
- }, [l, R]), M = o <= 0 ? !0 : n <= 1, C = o <= 0 ? !0 : n >= o, O = e <= 0 ? { start: 0, end: 0 } : {
66
- start: (n - 1) * u + 1,
67
- end: Math.min(n * u, e)
65
+ }, [m, v]), M = i <= 0 ? !0 : u <= 1, C = i <= 0 ? !0 : u >= i, V = e <= 0 ? { start: 0, end: 0 } : {
66
+ start: (u - 1) * c + 1,
67
+ end: Math.min(u * c, e)
68
68
  };
69
69
  return {
70
- page: n,
71
- pageSize: u,
72
- pageCount: o,
70
+ page: u,
71
+ pageSize: c,
72
+ pageCount: i,
73
73
  isFirstPage: M,
74
74
  isLastPage: C,
75
75
  hasPrevPage: !M,
76
76
  hasNextPage: !C,
77
- range: O,
78
- ...y
77
+ range: V,
78
+ ...O
79
79
  };
80
80
  }
81
81
  export {
82
- B as usePagination
82
+ H as usePagination
83
83
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxcustodia/library",
3
- "version": "2.0.0-alpha.31",
3
+ "version": "2.0.0-alpha.32",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.es.js",
@@ -324,6 +324,32 @@ describe("usePagination — Controllable Page", () => {
324
324
  expect(onPageChange).toHaveBeenCalledWith(3);
325
325
  });
326
326
 
327
+ it("Scenario 21b — onPageChange fires when controlled prop diverges from internal state then user navigates back to page 1", () => {
328
+ // Regression: if the page prop is updated externally (no internal navigation),
329
+ // pageState stays at its default (1). A subsequent goTo(1) calls setPageState(1)
330
+ // which is a React no-op → useEffect never fires → onPageChange silently skipped.
331
+ const onPageChange = vi.fn();
332
+ let controlledPage = 1;
333
+ const { result, rerender } = renderHook(() =>
334
+ usePagination({
335
+ totalItems: 100,
336
+ defaultPageSize: 10,
337
+ page: controlledPage,
338
+ onPageChange,
339
+ }),
340
+ );
341
+
342
+ // Parent externally jumps to page 5 — no internal navigation, pageState stays 1
343
+ controlledPage = 5;
344
+ rerender();
345
+ expect(result.current.page).toBe(5);
346
+ onPageChange.mockClear();
347
+
348
+ // User clicks "First" — should fire onPageChange(1)
349
+ act(() => result.current.goTo(1));
350
+ expect(onPageChange).toHaveBeenCalledWith(1);
351
+ });
352
+
327
353
  it("Scenario 22 — onPageChange NOT fired on mount", () => {
328
354
  const onPageChange = vi.fn();
329
355
  renderHook(() =>
@@ -136,6 +136,7 @@ export function usePagination({
136
136
 
137
137
  // Refs so memoized actions read live values without stale closures
138
138
  const pageRef = useLatestRef(page);
139
+ const pageStateRef = useLatestRef(pageState);
139
140
  const pageSizeRef = useLatestRef(pageSize);
140
141
  const pageCountRef = useLatestRef(pageCount);
141
142
  const onPageChangeRef = useLatestRef(onPageChange);
@@ -186,6 +187,14 @@ export function usePagination({
186
187
  const applyPage = (target: number) => {
187
188
  const clamped = clampPage(target, pageCountRef.current);
188
189
  if (clamped === pageRef.current) return; // no-op for same page
190
+ // In controlled mode, pageState may already equal `clamped` (diverged from
191
+ // pageProp), so setPageState would be a no-op and the useEffect that fires
192
+ // onPageChange would never run. Mirror the same direct-fire pattern used in
193
+ // setPageSize to guarantee the callback reaches the consumer.
194
+ if (pageStateRef.current === clamped) {
195
+ lastEmittedPageRef.current = clamped;
196
+ onPageChangeRef.current?.(clamped);
197
+ }
189
198
  setPageState(clamped);
190
199
  };
191
200