From 022bd7fe8dcafcb8d338f0e45dc8d814fddc59e7 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 12 May 2026 18:46:31 -0400 Subject: [PATCH 1/5] Show single number when pagination range is one item --- resources/js/components/ui/Pagination.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/resources/js/components/ui/Pagination.vue b/resources/js/components/ui/Pagination.vue index b68003b280e..6ff99861fbc 100644 --- a/resources/js/components/ui/Pagination.vue +++ b/resources/js/components/ui/Pagination.vue @@ -79,6 +79,11 @@ const showPerPageSelector = computed(() => props.showPerPageSelector && isPerPag const totalItems = computed(() => props.resourceMeta.total); const fromItem = computed(() => props.resourceMeta.from || 0); const toItem = computed(() => Math.min(props.resourceMeta.to, totalItems.value)); +const formattedRange = computed(() => { + return fromItem.value === toItem.value + ? Statamic.$number.format(fromItem.value) + : Statamic.$number.formatRange(fromItem.value, toItem.value); +}); function selectPage(page) { if (page === currentPage.value) { @@ -156,7 +161,7 @@ function getRange(start, end) {
{{ __(':range of :total', { - range: $number.formatRange(fromItem, toItem), + range: formattedRange, total: $number.format(totalItems) }) }}
From e33837a035cd7a5bf211a1ac0f590fc211d4b6a1 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 12 May 2026 18:54:42 -0400 Subject: [PATCH 2/5] Import NumberFormatter directly for test env --- resources/js/components/ui/Pagination.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/js/components/ui/Pagination.vue b/resources/js/components/ui/Pagination.vue index 6ff99861fbc..c6524acbffc 100644 --- a/resources/js/components/ui/Pagination.vue +++ b/resources/js/components/ui/Pagination.vue @@ -4,6 +4,7 @@ const normalizeInputOptions = HasInputOptions.methods.normalizeInputOptions; import { flatten, sortBy, range } from 'lodash-es'; import Select from './Select/Select.vue'; import Button from './Button/Button.vue'; +import NumberFormatter from '@/components/NumberFormatter.js'; import { computed } from 'vue'; const emit = defineEmits(['page-selected', 'per-page-changed']); @@ -81,8 +82,8 @@ const fromItem = computed(() => props.resourceMeta.from || 0); const toItem = computed(() => Math.min(props.resourceMeta.to, totalItems.value)); const formattedRange = computed(() => { return fromItem.value === toItem.value - ? Statamic.$number.format(fromItem.value) - : Statamic.$number.formatRange(fromItem.value, toItem.value); + ? NumberFormatter.format(fromItem.value) + : NumberFormatter.formatRange(fromItem.value, toItem.value); }); function selectPage(page) { From 8c197888d668b108cdce123a81f5c8eab0c255ef Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 13 May 2026 10:07:48 -0400 Subject: [PATCH 3/5] Import numberFormatter from @api --- resources/js/components/ui/Pagination.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/js/components/ui/Pagination.vue b/resources/js/components/ui/Pagination.vue index c6524acbffc..727190c6853 100644 --- a/resources/js/components/ui/Pagination.vue +++ b/resources/js/components/ui/Pagination.vue @@ -4,7 +4,7 @@ const normalizeInputOptions = HasInputOptions.methods.normalizeInputOptions; import { flatten, sortBy, range } from 'lodash-es'; import Select from './Select/Select.vue'; import Button from './Button/Button.vue'; -import NumberFormatter from '@/components/NumberFormatter.js'; +import { numberFormatter } from '@api'; import { computed } from 'vue'; const emit = defineEmits(['page-selected', 'per-page-changed']); @@ -82,8 +82,8 @@ const fromItem = computed(() => props.resourceMeta.from || 0); const toItem = computed(() => Math.min(props.resourceMeta.to, totalItems.value)); const formattedRange = computed(() => { return fromItem.value === toItem.value - ? NumberFormatter.format(fromItem.value) - : NumberFormatter.formatRange(fromItem.value, toItem.value); + ? numberFormatter.format(fromItem.value) + : numberFormatter.formatRange(fromItem.value, toItem.value); }); function selectPage(page) { From adec42e5c4e413ee306852f03bef70056aca9c3e Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 13 May 2026 10:19:19 -0400 Subject: [PATCH 4/5] Alias numberFormatter as $number --- resources/js/components/ui/Pagination.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/js/components/ui/Pagination.vue b/resources/js/components/ui/Pagination.vue index 727190c6853..f0614fddd35 100644 --- a/resources/js/components/ui/Pagination.vue +++ b/resources/js/components/ui/Pagination.vue @@ -4,7 +4,7 @@ const normalizeInputOptions = HasInputOptions.methods.normalizeInputOptions; import { flatten, sortBy, range } from 'lodash-es'; import Select from './Select/Select.vue'; import Button from './Button/Button.vue'; -import { numberFormatter } from '@api'; +import { numberFormatter as $number } from '@api'; import { computed } from 'vue'; const emit = defineEmits(['page-selected', 'per-page-changed']); @@ -82,8 +82,8 @@ const fromItem = computed(() => props.resourceMeta.from || 0); const toItem = computed(() => Math.min(props.resourceMeta.to, totalItems.value)); const formattedRange = computed(() => { return fromItem.value === toItem.value - ? numberFormatter.format(fromItem.value) - : numberFormatter.formatRange(fromItem.value, toItem.value); + ? $number.format(fromItem.value) + : $number.formatRange(fromItem.value, toItem.value); }); function selectPage(page) { From f9da05d226bac54e69f598a67f1477757dcd1c3b Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Wed, 13 May 2026 10:34:53 -0400 Subject: [PATCH 5/5] Collapse equal range endpoints to a single number --- resources/js/components/NumberFormatter.js | 8 +++++--- resources/js/components/ui/Pagination.vue | 8 +------- resources/js/tests/components/NumberFormatter.test.js | 11 +++++++++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/resources/js/components/NumberFormatter.js b/resources/js/components/NumberFormatter.js index 4f26b6042b4..af166851c6d 100644 --- a/resources/js/components/NumberFormatter.js +++ b/resources/js/components/NumberFormatter.js @@ -36,11 +36,13 @@ export default class NumberFormatter { toString() { try { - if (this.#rangeEnd !== undefined) { - return Intl.NumberFormat(this.locale, this.#options).formatRange(this.#number, this.#rangeEnd); + const formatter = Intl.NumberFormat(this.locale, this.#options); + + if (this.#rangeEnd !== undefined && this.#rangeEnd !== this.#number) { + return formatter.formatRange(this.#number, this.#rangeEnd); } - return Intl.NumberFormat(this.locale, this.#options).format(this.#number); + return formatter.format(this.#number); } catch (e) { return 'Invalid Number'; } diff --git a/resources/js/components/ui/Pagination.vue b/resources/js/components/ui/Pagination.vue index f0614fddd35..b68003b280e 100644 --- a/resources/js/components/ui/Pagination.vue +++ b/resources/js/components/ui/Pagination.vue @@ -4,7 +4,6 @@ const normalizeInputOptions = HasInputOptions.methods.normalizeInputOptions; import { flatten, sortBy, range } from 'lodash-es'; import Select from './Select/Select.vue'; import Button from './Button/Button.vue'; -import { numberFormatter as $number } from '@api'; import { computed } from 'vue'; const emit = defineEmits(['page-selected', 'per-page-changed']); @@ -80,11 +79,6 @@ const showPerPageSelector = computed(() => props.showPerPageSelector && isPerPag const totalItems = computed(() => props.resourceMeta.total); const fromItem = computed(() => props.resourceMeta.from || 0); const toItem = computed(() => Math.min(props.resourceMeta.to, totalItems.value)); -const formattedRange = computed(() => { - return fromItem.value === toItem.value - ? $number.format(fromItem.value) - : $number.formatRange(fromItem.value, toItem.value); -}); function selectPage(page) { if (page === currentPage.value) { @@ -162,7 +156,7 @@ function getRange(start, end) {
{{ __(':range of :total', { - range: formattedRange, + range: $number.formatRange(fromItem, toItem), total: $number.format(totalItems) }) }}
diff --git a/resources/js/tests/components/NumberFormatter.test.js b/resources/js/tests/components/NumberFormatter.test.js index 3be37bd475b..e746a0f9845 100644 --- a/resources/js/tests/components/NumberFormatter.test.js +++ b/resources/js/tests/components/NumberFormatter.test.js @@ -203,6 +203,17 @@ describe('formatRange', () => { test('it normalizes string inputs', () => { expect(new NumberFormatter().formatRange('1000', '5000')).toBe('1,000–5,000'); }); + + test('it formats a single number when the range endpoints are equal', () => { + expect(new NumberFormatter().formatRange(1, 1)).toBe('1'); + expect(new NumberFormatter().formatRange('5', 5)).toBe('5'); + expect(new NumberFormatter().formatRange(0.5, 0.5, 'percent')).toBe('50%'); + }); + + test('it keeps the approximation when endpoints differ but format identically', () => { + expect(new NumberFormatter().formatRange(1, 1.3, { maximumFractionDigits: 0 })).toBe('~1'); + expect(new NumberFormatter().formatRange(1.231, 1.234, { maximumFractionDigits: 2 })).toBe('~1.23'); + }); }); describe('range via array syntax', () => {