From 499c3df3e89e96156893c60362b125dc7dbf1c08 Mon Sep 17 00:00:00 2001 From: Alex Iglesias Date: Fri, 29 Aug 2025 19:51:16 +0200 Subject: [PATCH 1/3] feat: granular number and date formatting options for tags --- .changeset/fast-onions-march.md | 5 ++ packages/list/src/filter/tags.ts | 103 +++++++++++++++++++++------ packages/list/src/utils/constants.ts | 39 ++++++++++ 3 files changed, 124 insertions(+), 23 deletions(-) create mode 100644 .changeset/fast-onions-march.md diff --git a/.changeset/fast-onions-march.md b/.changeset/fast-onions-march.md new file mode 100644 index 000000000..00e4c34db --- /dev/null +++ b/.changeset/fast-onions-march.md @@ -0,0 +1,5 @@ +--- +'@finsweet/attributes-list': minor +--- + +feat: granular number and date formatting options for tags diff --git a/packages/list/src/filter/tags.ts b/packages/list/src/filter/tags.ts index 0c98896a1..3b94eb486 100644 --- a/packages/list/src/filter/tags.ts +++ b/packages/list/src/filter/tags.ts @@ -1,4 +1,4 @@ -import { addListener, cloneNode, isDate, isNotEmpty, isNumber } from '@finsweet/attributes-utils'; +import { addListener, cloneNode, type FormFieldType, isDate, isNotEmpty, isNumber } from '@finsweet/attributes-utils'; import { watch } from '@vue/reactivity'; import type { List } from '../components/List'; @@ -275,31 +275,52 @@ const populateTag = (condition: FiltersCondition, tagData: TagData) => { const formatDisplay = getAttribute(valueElement, 'formatdisplay'); if (formatDisplay) { const locale = formatDisplay === 'true' ? undefined : formatDisplay; + const options: Intl.NumberFormatOptions & Intl.DateTimeFormatOptions = { + calendar: getAttribute(valueElement, 'formatcalendar'), + compactDisplay: getAttribute(valueElement, 'formatcompactdisplay'), + currency: getAttribute(valueElement, 'formatcurrency'), + currencyDisplay: getAttribute(valueElement, 'formatcurrencydisplay'), + currencySign: getAttribute(valueElement, 'formatcurrencysign'), + dateStyle: getAttribute(valueElement, 'formatdatestyle'), + day: getAttribute(valueElement, 'formatday'), + dayPeriod: getAttribute(valueElement, 'formatdayperiod'), + era: getAttribute(valueElement, 'formatera'), + formatMatcher: getAttribute(valueElement, 'formatformatmatcher'), + fractionalSecondDigits: getAttribute(valueElement, 'formatfractionalseconddigits'), + hour: getAttribute(valueElement, 'formathour'), + hour12: getAttribute(valueElement, 'formathour12'), + hourCycle: getAttribute(valueElement, 'formathourcycle'), + localeMatcher: getAttribute(valueElement, 'formatlocalematcher'), + maximumFractionDigits: getAttribute(valueElement, 'formatmaximumfractiondigits'), + maximumSignificantDigits: getAttribute(valueElement, 'formatmaximumsignificantdigits'), + minimumFractionDigits: getAttribute(valueElement, 'formatminimumfractiondigits'), + minimumIntegerDigits: getAttribute(valueElement, 'formatminimumintegerdigits'), + minimumSignificantDigits: getAttribute(valueElement, 'formatminimumsignificantdigits'), + minute: getAttribute(valueElement, 'formatminute'), + month: getAttribute(valueElement, 'formatmonth'), + notation: getAttribute(valueElement, 'formatnotation'), + numberingSystem: getAttribute(valueElement, 'formatnumberingsystem'), + roundingIncrement: getAttribute(valueElement, 'formatroundingincrement'), + roundingMode: getAttribute(valueElement, 'formatroundingmode'), + roundingPriority: getAttribute(valueElement, 'formatroundingpriority'), + second: getAttribute(valueElement, 'formatsecond'), + signDisplay: getAttribute(valueElement, 'formatsigndisplay'), + style: getAttribute(valueElement, 'formatstyle'), + timeStyle: getAttribute(valueElement, 'formattimestyle'), + timeZone: getAttribute(valueElement, 'formattimezone'), + timeZoneName: getAttribute(valueElement, 'formattimezonename'), + trailingZeroDisplay: getAttribute(valueElement, 'formattrailingzerodisplay'), + unit: getAttribute(valueElement, 'formatunit'), + unitDisplay: getAttribute(valueElement, 'formatunitdisplay'), + useGrouping: getAttribute(valueElement, 'formatusegrouping'), + weekday: getAttribute(valueElement, 'formatweekday'), + year: getAttribute(valueElement, 'formatyear'), + }; if (Array.isArray(condition.value)) { - formattedValue = condition.value.map((value) => { - const parsedValue = parseFilterValue(value, condition.type); - - if (isNumber(parsedValue)) { - return parsedValue.toLocaleString(locale); - } - - if (isDate(parsedValue)) { - return parsedValue.toLocaleDateString(locale); - } - - return value; - }); + formattedValue = condition.value.map((value) => formatValue(value, condition.type, locale, options)); } else { - const parsedValue = parseFilterValue(condition.value, condition.type); - - if (isNumber(parsedValue)) { - formattedValue = parsedValue.toLocaleString(locale); - } - - if (isDate(parsedValue)) { - formattedValue = parsedValue.toLocaleDateString(locale); - } + formattedValue = formatValue(condition.value, condition.type, locale, options); } } @@ -312,3 +333,39 @@ const populateTag = (condition: FiltersCondition, tagData: TagData) => { } } }; + +/** + * Formats a value based on the locale and options. + * @param value + * @param type + * @param rawLocale + * @param options + * @returns The formatted value + */ +const formatValue = ( + value: string, + type: FormFieldType, + rawLocale: string | undefined, + options: Intl.NumberFormatOptions & Intl.DateTimeFormatOptions +) => { + const locale = rawLocale === 'true' ? undefined : rawLocale; + const parsedValue = parseFilterValue(value, type); + + if (isNumber(parsedValue)) { + try { + return new Intl.NumberFormat(locale, options).format(parsedValue); + } catch { + return new Intl.NumberFormat(window.navigator?.language || undefined, options).format(parsedValue); + } + } + + if (isDate(parsedValue)) { + try { + return new Intl.DateTimeFormat(locale, options).format(parsedValue); + } catch { + return new Intl.DateTimeFormat(window.navigator?.language || undefined, options).format(parsedValue); + } + } + + return value; +}; diff --git a/packages/list/src/utils/constants.ts b/packages/list/src/utils/constants.ts index 8bb3e92fb..1900fa501 100644 --- a/packages/list/src/utils/constants.ts +++ b/packages/list/src/utils/constants.ts @@ -587,6 +587,45 @@ export const SETTINGS = { * A specific locale can be forced using IETF BCP 47 language tags like "en-US". */ formatdisplay: { key: 'formatdisplay', values: ['true'] }, + formatcompactdisplay: { key: 'formatcompactdisplay' }, + formatcurrency: { key: 'formatcurrency' }, + formatcurrencydisplay: { key: 'formatcurrencydisplay' }, + formatcurrencysign: { key: 'formatcurrencysign' }, + formatcalendar: { key: 'formatcalendar' }, + formatdatestyle: { key: 'formatdatestyle' }, + formatday: { key: 'formatday' }, + formatdayperiod: { key: 'formatdayperiod' }, + formatera: { key: 'formatera' }, + formatformatmatcher: { key: 'formatformatmatcher' }, + formatfractionalseconddigits: { key: 'formatfractionalseconddigits', isNumeric: true }, + formathour: { key: 'formathour' }, + formathour12: { key: 'formathour12' }, + formathourcycle: { key: 'formathourcycle' }, + formatlocalematcher: { key: 'formatlocalematcher' }, + formatminute: { key: 'formatminute' }, + formatmonth: { key: 'formatmonth' }, + formatmaximumsignificantdigits: { key: 'formatmaximumsignificantdigits', isNumeric: true }, + formatmaximumfractiondigits: { key: 'formatmaximumfractiondigits', isNumeric: true }, + formatminimumfractiondigits: { key: 'formatminimumfractiondigits', isNumeric: true }, + formatminimumintegerdigits: { key: 'formatminimumintegerdigits', isNumeric: true }, + formatminimumsignificantdigits: { key: 'formatminimumsignificantdigits', isNumeric: true }, + formatnotation: { key: 'formatnotation' }, + formatnumberingsystem: { key: 'formatnumberingsystem' }, + formatroundingpriority: { key: 'formatroundingpriority' }, + formatroundingincrement: { key: 'formatroundingincrement', isNumeric: true }, + formatroundingmode: { key: 'formatroundingmode' }, + formatsecond: { key: 'formatsecond' }, + formatsigndisplay: { key: 'formatsigndisplay' }, + formatstyle: { key: 'formatstyle' }, + formattimestyle: { key: 'formattimestyle' }, + formattimezone: { key: 'formattimezone' }, + formattimezonename: { key: 'formattimezonename' }, + formattrailingzerodisplay: { key: 'formattrailingzerodisplay' }, + formatunit: { key: 'formatunit' }, + formatunitdisplay: { key: 'formatunitdisplay' }, + formatusegrouping: { key: 'formatusegrouping' }, + formatweekday: { key: 'formatweekday' }, + formatyear: { key: 'formatyear' }, /** * Defines the position for a static list item. From c868f2d26a07fe6e23db7c6b08183092c19f97f8 Mon Sep 17 00:00:00 2001 From: Alex Iglesias Date: Mon, 1 Sep 2025 21:02:22 +0200 Subject: [PATCH 2/3] fix: boolean values --- packages/list/src/filter/tags.ts | 6 +++--- packages/list/src/utils/constants.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/list/src/filter/tags.ts b/packages/list/src/filter/tags.ts index 3b94eb486..088d04ace 100644 --- a/packages/list/src/filter/tags.ts +++ b/packages/list/src/filter/tags.ts @@ -3,7 +3,7 @@ import { watch } from '@vue/reactivity'; import type { List } from '../components/List'; import { SETTINGS } from '../utils/constants'; -import { getAttribute, getElementSelector, queryElement } from '../utils/selectors'; +import { getAttribute, getElementSelector, hasAttributeValue, queryElement } from '../utils/selectors'; import type { FilterOperator, Filters, FiltersCondition } from './types'; import { parseFilterValue } from './utils'; @@ -288,7 +288,7 @@ const populateTag = (condition: FiltersCondition, tagData: TagData) => { formatMatcher: getAttribute(valueElement, 'formatformatmatcher'), fractionalSecondDigits: getAttribute(valueElement, 'formatfractionalseconddigits'), hour: getAttribute(valueElement, 'formathour'), - hour12: getAttribute(valueElement, 'formathour12'), + hour12: hasAttributeValue(valueElement, 'formathour12', 'true'), hourCycle: getAttribute(valueElement, 'formathourcycle'), localeMatcher: getAttribute(valueElement, 'formatlocalematcher'), maximumFractionDigits: getAttribute(valueElement, 'formatmaximumfractiondigits'), @@ -312,7 +312,7 @@ const populateTag = (condition: FiltersCondition, tagData: TagData) => { trailingZeroDisplay: getAttribute(valueElement, 'formattrailingzerodisplay'), unit: getAttribute(valueElement, 'formatunit'), unitDisplay: getAttribute(valueElement, 'formatunitdisplay'), - useGrouping: getAttribute(valueElement, 'formatusegrouping'), + useGrouping: hasAttributeValue(valueElement, 'formatusegrouping', 'true'), weekday: getAttribute(valueElement, 'formatweekday'), year: getAttribute(valueElement, 'formatyear'), }; diff --git a/packages/list/src/utils/constants.ts b/packages/list/src/utils/constants.ts index 1900fa501..7bd95c349 100644 --- a/packages/list/src/utils/constants.ts +++ b/packages/list/src/utils/constants.ts @@ -599,7 +599,7 @@ export const SETTINGS = { formatformatmatcher: { key: 'formatformatmatcher' }, formatfractionalseconddigits: { key: 'formatfractionalseconddigits', isNumeric: true }, formathour: { key: 'formathour' }, - formathour12: { key: 'formathour12' }, + formathour12: { key: 'formathour12', values: ['true'] }, formathourcycle: { key: 'formathourcycle' }, formatlocalematcher: { key: 'formatlocalematcher' }, formatminute: { key: 'formatminute' }, @@ -623,7 +623,7 @@ export const SETTINGS = { formattrailingzerodisplay: { key: 'formattrailingzerodisplay' }, formatunit: { key: 'formatunit' }, formatunitdisplay: { key: 'formatunitdisplay' }, - formatusegrouping: { key: 'formatusegrouping' }, + formatusegrouping: { key: 'formatusegrouping', values: ['true'] }, formatweekday: { key: 'formatweekday' }, formatyear: { key: 'formatyear' }, From 6c49922fb04ebda33b254fa7812d58c92f0d9987 Mon Sep 17 00:00:00 2001 From: Alex Iglesias Date: Tue, 2 Sep 2025 10:02:02 +0200 Subject: [PATCH 3/3] fixes --- packages/list/src/filter/tags.ts | 6 ++++-- packages/list/src/utils/constants.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/list/src/filter/tags.ts b/packages/list/src/filter/tags.ts index 088d04ace..f9b2af4a6 100644 --- a/packages/list/src/filter/tags.ts +++ b/packages/list/src/filter/tags.ts @@ -288,7 +288,9 @@ const populateTag = (condition: FiltersCondition, tagData: TagData) => { formatMatcher: getAttribute(valueElement, 'formatformatmatcher'), fractionalSecondDigits: getAttribute(valueElement, 'formatfractionalseconddigits'), hour: getAttribute(valueElement, 'formathour'), - hour12: hasAttributeValue(valueElement, 'formathour12', 'true'), + hour12: getAttribute(valueElement, 'formathour12') + ? hasAttributeValue(valueElement, 'formathour12', 'true') + : undefined, hourCycle: getAttribute(valueElement, 'formathourcycle'), localeMatcher: getAttribute(valueElement, 'formatlocalematcher'), maximumFractionDigits: getAttribute(valueElement, 'formatmaximumfractiondigits'), @@ -312,7 +314,7 @@ const populateTag = (condition: FiltersCondition, tagData: TagData) => { trailingZeroDisplay: getAttribute(valueElement, 'formattrailingzerodisplay'), unit: getAttribute(valueElement, 'formatunit'), unitDisplay: getAttribute(valueElement, 'formatunitdisplay'), - useGrouping: hasAttributeValue(valueElement, 'formatusegrouping', 'true'), + useGrouping: getAttribute(valueElement, 'formatusegrouping'), weekday: getAttribute(valueElement, 'formatweekday'), year: getAttribute(valueElement, 'formatyear'), }; diff --git a/packages/list/src/utils/constants.ts b/packages/list/src/utils/constants.ts index 7bd95c349..bfa2b8fec 100644 --- a/packages/list/src/utils/constants.ts +++ b/packages/list/src/utils/constants.ts @@ -623,7 +623,7 @@ export const SETTINGS = { formattrailingzerodisplay: { key: 'formattrailingzerodisplay' }, formatunit: { key: 'formatunit' }, formatunitdisplay: { key: 'formatunitdisplay' }, - formatusegrouping: { key: 'formatusegrouping', values: ['true'] }, + formatusegrouping: { key: 'formatusegrouping' }, formatweekday: { key: 'formatweekday' }, formatyear: { key: 'formatyear' },