@datagouv/components-next 1.0.2-dev.188 → 1.0.2-dev.189

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,4 +1,4 @@
1
- import { g as Ke } from "./main-BgsOe_n1.js";
1
+ import { g as Ke } from "./main-DseJBKsd.js";
2
2
  import We from "vue";
3
3
  function Fe($, K) {
4
4
  for (var V = 0; V < K.length; V++) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datagouv/components-next",
3
- "version": "1.0.2-dev.188",
3
+ "version": "1.0.2-dev.189",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "engines": {
@@ -19,7 +19,7 @@
19
19
  </li>
20
20
  <li>
21
21
  <a
22
- :href="getHref(page - 1)"
22
+ :href="getHref(currentPage - 1)"
23
23
  class="fr-pagination__link fr-pagination__link--prev fr-pagination__link--lg-label"
24
24
  data-testid="previous-page"
25
25
  @click.prevent.stop="previousPage"
@@ -29,10 +29,10 @@
29
29
  </li>
30
30
  <li>
31
31
  <a
32
- :aria-current="page === 1 ? 'page' : undefined"
32
+ :aria-current="currentPage === 1 ? 'page' : undefined"
33
33
  :href="getHref(1)"
34
34
  class="fr-pagination__link"
35
- :class="{ 'fr-hidden fr-unhidden-sm': page > 1 }"
35
+ :class="{ 'fr-hidden fr-unhidden-sm': currentPage > 1 }"
36
36
  :title="t('Page {nb}', { nb: 1 })"
37
37
  :data-testid="1"
38
38
  @click.prevent.stop="onClick(1)"
@@ -47,8 +47,8 @@
47
47
  <a
48
48
  v-if="index"
49
49
  class="fr-pagination__link"
50
- :class="{ 'fr-hidden fr-unhidden-lg': index < page - 1 || index > page + 1 }"
51
- :aria-current="page === index ? 'page' : undefined"
50
+ :class="{ 'fr-hidden fr-unhidden-lg': index < currentPage - 1 || index > currentPage + 1 }"
51
+ :aria-current="currentPage === index ? 'page' : undefined"
52
52
  :href="getHref(index)"
53
53
  :title="t('Page {nb}', { nb: index })"
54
54
  :data-testid="index"
@@ -66,7 +66,7 @@
66
66
  <li>
67
67
  <a
68
68
  class="fr-pagination__link"
69
- :aria-current="page === pageCount ? 'page' : undefined"
69
+ :aria-current="currentPage === pageCount ? 'page' : undefined"
70
70
  :href="getHref(pageCount)"
71
71
  :title="t('Page {nb}', { nb: pageCount })"
72
72
  :data-testid="pageCount"
@@ -78,7 +78,7 @@
78
78
  <li>
79
79
  <a
80
80
  class="fr-pagination__link fr-pagination__link--next fr-pagination__link--lg-label"
81
- :href="getHref(page + 1)"
81
+ :href="getHref(currentPage + 1)"
82
82
  data-testid="next-page"
83
83
  @click.prevent.stop="nextPage"
84
84
  >
@@ -103,6 +103,7 @@
103
103
  import { computed, useTemplateRef } from 'vue'
104
104
  import { useRoute } from 'vue-router'
105
105
  import { useTranslation } from '../composables/useTranslation'
106
+ import { clampPage, getVisiblePages } from '../functions/paginate'
106
107
 
107
108
  type Props = {
108
109
  /**
@@ -128,52 +129,13 @@ const props = withDefaults(defineProps<Props>(), {
128
129
  pageSize: 20,
129
130
  })
130
131
 
131
- const PAGES_AROUND = 3
132
-
133
- function range(size: number, startAt = 1) {
134
- return [...Array(size).keys()].map(i => i + startAt)
135
- }
136
-
137
- function getPages(pageCount: number) {
138
- return range(pageCount)
139
- }
140
-
141
- function getPagesShown(pages: { length: number }, currentPage: number) {
142
- return Math.min(
143
- PAGES_AROUND * 2 + 1,
144
- pages.length - 2,
145
- PAGES_AROUND + currentPage - 1,
146
- PAGES_AROUND + pages.length - currentPage,
147
- )
148
- }
149
-
150
- function getStartPage(currentPage: number) {
151
- return Math.max(
152
- currentPage - PAGES_AROUND, // we want to start 3 pages before the current one
153
- 2, // we don't want to start below page 2
154
- )
155
- }
156
-
157
- function getVisiblePages(currentPage: number, pageCount: number) {
158
- const pages = getPages(pageCount)
159
- const start = getStartPage(currentPage)
160
- if (pageCount <= 2) {
161
- return []
162
- }
163
- const pagination: Array<number | null> = range(getPagesShown(pages, currentPage), start)
164
- if (!pagination.includes(2)) {
165
- pagination.unshift(null)
166
- }
167
- if (!pagination.includes(pageCount - 1)) {
168
- pagination.push(null)
169
- }
170
- return pagination
171
- }
172
-
173
132
  const { t } = useTranslation()
174
133
  const route = useRoute()
175
134
  const pageCount = computed(() => Math.ceil(props.totalResults / props.pageSize))
176
- const visiblePages = computed(() => getVisiblePages(props.page, pageCount.value))
135
+ // The `page` prop often comes straight from a query string, so it is normalised
136
+ // once here and every link below builds on an existing page.
137
+ const currentPage = computed(() => clampPage(props.page, pageCount.value))
138
+ const visiblePages = computed(() => getVisiblePages(currentPage.value, pageCount.value))
177
139
 
178
140
  const nav = useTemplateRef('navRef')
179
141
  function change(index: number) {
@@ -186,20 +148,20 @@ function change(index: number) {
186
148
  }
187
149
 
188
150
  function onClick(index: number) {
189
- if (index !== props.page) {
151
+ if (index !== currentPage.value) {
190
152
  change(index)
191
153
  }
192
154
  }
193
155
 
194
156
  function nextPage() {
195
- const index = props.page + 1
157
+ const index = currentPage.value + 1
196
158
  if (index <= pageCount.value) {
197
159
  change(index)
198
160
  }
199
161
  }
200
162
 
201
163
  function previousPage() {
202
- const index = props.page - 1
164
+ const index = currentPage.value - 1
203
165
  if (index > 0) {
204
166
  change(index)
205
167
  }
@@ -209,7 +171,7 @@ function getHref(forPage: number) {
209
171
  if (forPage < 1 || forPage > pageCount.value) {
210
172
  return undefined
211
173
  }
212
- if (props.page === forPage) {
174
+ if (currentPage.value === forPage) {
213
175
  return undefined
214
176
  }
215
177
  const search = new URLSearchParams(route.query as Record<string, string>)
@@ -0,0 +1,46 @@
1
+ export const PAGES_AROUND = 3
2
+
3
+ function range(size: number, startAt: number) {
4
+ return [...Array(size).keys()].map(i => i + startAt)
5
+ }
6
+
7
+ /**
8
+ * Brings whatever the caller holds as a current page back to a page that exists.
9
+ * The page number usually comes from a query string, so it can be anything:
10
+ * `?page=abc` (NaN), `?page=-5`, `?page=1.5`, or a stale link to a page far past
11
+ * the last one.
12
+ */
13
+ export function clampPage(page: number, pageCount: number) {
14
+ if (!Number.isFinite(page)) {
15
+ return 1
16
+ }
17
+ return Math.min(Math.max(Math.trunc(page), 1), Math.max(pageCount, 1))
18
+ }
19
+
20
+ /**
21
+ * The middle window of the pagination: the pages around the current one, with
22
+ * `null` marking an ellipsis. The first and last pages are rendered by the
23
+ * component itself and are never part of this window.
24
+ */
25
+ export function getVisiblePages(currentPage: number, pageCount: number) {
26
+ if (pageCount <= 2) {
27
+ return []
28
+ }
29
+
30
+ const page = clampPage(currentPage, pageCount)
31
+ const size = Math.min(
32
+ PAGES_AROUND * 2 + 1,
33
+ pageCount - 2, // the first and last pages are rendered outside of the window
34
+ PAGES_AROUND + page - 1, // don't reach below page 2
35
+ PAGES_AROUND + pageCount - page, // don't reach past the last page
36
+ )
37
+ const pagination: Array<number | null> = range(size, Math.max(page - PAGES_AROUND, 2))
38
+
39
+ if (!pagination.includes(2)) {
40
+ pagination.unshift(null)
41
+ }
42
+ if (!pagination.includes(pageCount - 1)) {
43
+ pagination.push(null)
44
+ }
45
+ return pagination
46
+ }