@djangocfg/ui-core 2.1.574 → 2.1.576

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.574",
3
+ "version": "2.1.576",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -130,7 +130,7 @@
130
130
  "check:contrast": "node scripts/check-preset-contrast.mjs"
131
131
  },
132
132
  "peerDependencies": {
133
- "@djangocfg/i18n": "^2.1.574",
133
+ "@djangocfg/i18n": "^2.1.576",
134
134
  "consola": "^3.4.2",
135
135
  "lucide-react": "^0.545.0",
136
136
  "moment": "^2.30.1",
@@ -206,9 +206,9 @@
206
206
  "vaul": "1.1.2"
207
207
  },
208
208
  "devDependencies": {
209
- "@djangocfg/eslint-config": "^2.1.574",
210
- "@djangocfg/i18n": "^2.1.574",
211
- "@djangocfg/typescript-config": "^2.1.574",
209
+ "@djangocfg/eslint-config": "^2.1.576",
210
+ "@djangocfg/i18n": "^2.1.576",
211
+ "@djangocfg/typescript-config": "^2.1.576",
212
212
  "@storybook/react-vite": "^10.5.0",
213
213
  "@types/node": "^24.13.3",
214
214
  "@types/react": "19.2.15",
@@ -9,7 +9,7 @@ import { useIsMobile } from '../../../hooks';
9
9
  import { useLocation, useQueryParams } from '../../../hooks/router';
10
10
 
11
11
  import {
12
- Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink,
12
+ Pagination, PaginationContent, PaginationItem, PaginationLink,
13
13
  PaginationNext, PaginationPrevious
14
14
  } from './pagination';
15
15
 
@@ -64,16 +64,23 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
64
64
  const t = useAppT();
65
65
  const { locale } = useI18n();
66
66
 
67
- const getCurrentPageFromUrl = (): number => {
67
+ /* The URL wins when it carries a page, the prop otherwise.
68
+ *
69
+ * During the server render `useQueryParams()` is empty, so the old
70
+ * `getCurrentPageFromUrl() || currentPage` always resolved to 1 — the
71
+ * fallback to the prop could never run, because the function returned 1
72
+ * rather than a falsy value. Every server-rendered page built its number
73
+ * window around page 1: `/search?page=100` linked 2–6 and the last page,
74
+ * never 99 or 101. Pages 7..N-1 were linked from nowhere in the HTML a
75
+ * crawler reads, which is the only HTML a crawler reads. */
76
+ const getCurrentPageFromUrl = (): number | null => {
68
77
  const pageParam = queryParams.get('page');
69
- if (pageParam) {
70
- const pageNum = parseInt(pageParam, 10);
71
- return isNaN(pageNum) ? 1 : pageNum;
72
- }
73
- return 1;
78
+ if (!pageParam) return null;
79
+ const pageNum = parseInt(pageParam, 10);
80
+ return isNaN(pageNum) || pageNum < 1 ? null : pageNum;
74
81
  };
75
82
 
76
- const actualCurrentPage = getCurrentPageFromUrl() || currentPage;
83
+ const actualCurrentPage = getCurrentPageFromUrl() ?? currentPage;
77
84
  const actualHasPreviousPage = actualCurrentPage > 1;
78
85
 
79
86
  const getPageUrl = (page: number): string => {
@@ -99,7 +106,16 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
99
106
  * cheap to build and answers a question no one asks.
100
107
  *
101
108
  * Two chevrons and the numbers. Anything more is furniture. */
102
- const getVisiblePages = (): (number | 'ellipsis')[] => {
109
+ /* An ellipsis is a link to the MIDDLE of the pages it hides.
110
+ *
111
+ * It used to be inert text, so the only pages a crawler could reach were the
112
+ * ones in the window, one step at a time: 267 pages took ~90 hops. Linking
113
+ * each gap's midpoint halves the unexplored range with every hop, so any
114
+ * page is ~log2(N) hops from page 1 (8 for 267). A reader gets a "jump
115
+ * roughly there" control, which is what an ellipsis promises anyway. */
116
+ type PageItem = number | { ellipsis: number };
117
+
118
+ const getVisiblePages = (): PageItem[] => {
103
119
  const mobileMaxVisible = 3;
104
120
  const effectiveMaxVisible = isMobile ? mobileMaxVisible : maxVisiblePages;
105
121
 
@@ -107,7 +123,7 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
107
123
  return Array.from({ length: totalPages }, (_, i) => i + 1);
108
124
  }
109
125
 
110
- const pages: (number | 'ellipsis')[] = [];
126
+ const pages: PageItem[] = [];
111
127
  const halfVisible = Math.floor(effectiveMaxVisible / 2);
112
128
 
113
129
  if (isMobile) {
@@ -126,9 +142,11 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
126
142
  start = Math.max(2, totalPages - effectiveMaxVisible + 2);
127
143
  }
128
144
 
129
- if (start > 2) pages.push('ellipsis');
145
+ if (start > 2) pages.push({ ellipsis: Math.floor((2 + start - 1) / 2) });
130
146
  for (let i = start; i <= end; i++) pages.push(i);
131
- if (end < totalPages - 1) pages.push('ellipsis');
147
+ if (end < totalPages - 1) {
148
+ pages.push({ ellipsis: Math.floor((end + 1 + totalPages - 1) / 2) });
149
+ }
132
150
  if (totalPages > 1) pages.push(totalPages);
133
151
  }
134
152
 
@@ -173,8 +191,14 @@ export const SSRPagination: React.FC<SSRPaginationProps> = ({
173
191
 
174
192
  {visiblePages.map((page, index) => (
175
193
  <PaginationItem key={index}>
176
- {page === 'ellipsis' ? (
177
- <PaginationEllipsis />
194
+ {typeof page === 'object' ? (
195
+ <PaginationLink
196
+ href={getPageUrl(page.ellipsis)}
197
+ aria-label={t('ui.pagination.page', { page: page.ellipsis })}
198
+ scroll
199
+ >
200
+ …
201
+ </PaginationLink>
178
202
  ) : (
179
203
  <PaginationLink
180
204
  href={getPageUrl(page)}