@proyecto-viviana/solidaria 0.2.4 → 0.2.8
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/LICENSE +21 -0
- package/dist/actiongroup/createActionGroup.d.ts +29 -0
- package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
- package/dist/actiongroup/index.d.ts +2 -0
- package/dist/actiongroup/index.d.ts.map +1 -0
- package/dist/autocomplete/createAutocomplete.d.ts +6 -2
- package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
- package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
- package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
- package/dist/button/createToggleButtonGroup.d.ts +32 -0
- package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
- package/dist/button/index.d.ts +2 -0
- package/dist/button/index.d.ts.map +1 -1
- package/dist/calendar/createCalendarCell.d.ts +2 -0
- package/dist/calendar/createCalendarCell.d.ts.map +1 -1
- package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
- package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
- package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
- package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
- package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
- package/dist/collections/index.d.ts +56 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/color/createColorArea.d.ts.map +1 -1
- package/dist/color/createColorSlider.d.ts.map +1 -1
- package/dist/color/createColorWheel.d.ts.map +1 -1
- package/dist/combobox/createComboBox.d.ts +6 -0
- package/dist/combobox/createComboBox.d.ts.map +1 -1
- package/dist/datepicker/createDatePicker.d.ts +6 -0
- package/dist/datepicker/createDatePicker.d.ts.map +1 -1
- package/dist/datepicker/createDateRangePicker.d.ts +40 -0
- package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
- package/dist/datepicker/createDateSegment.d.ts +1 -1
- package/dist/datepicker/createDateSegment.d.ts.map +1 -1
- package/dist/datepicker/createTimeSegment.d.ts +29 -0
- package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
- package/dist/datepicker/index.d.ts +2 -0
- package/dist/datepicker/index.d.ts.map +1 -1
- package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
- package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
- package/dist/dnd/createDrag.d.ts.map +1 -1
- package/dist/dnd/createDraggableCollection.d.ts +4 -0
- package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
- package/dist/dnd/createDraggableItem.d.ts.map +1 -1
- package/dist/dnd/createDrop.d.ts.map +1 -1
- package/dist/dnd/createDroppableCollection.d.ts +32 -1
- package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
- package/dist/dnd/createDroppableItem.d.ts.map +1 -1
- package/dist/dnd/index.d.ts +1 -1
- package/dist/dnd/index.d.ts.map +1 -1
- package/dist/grid/createGrid.d.ts.map +1 -1
- package/dist/gridlist/createGridList.d.ts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4659 -3452
- package/dist/index.js.map +1 -7
- package/dist/index.ssr.js +4659 -3452
- package/dist/index.ssr.js.map +1 -7
- package/dist/interactions/createFocus.d.ts.map +1 -1
- package/dist/interactions/createFocusWithin.d.ts.map +1 -1
- package/dist/link/createLink.d.ts +10 -0
- package/dist/link/createLink.d.ts.map +1 -1
- package/dist/listbox/createListBox.d.ts +1 -0
- package/dist/listbox/createListBox.d.ts.map +1 -1
- package/dist/listbox/createOption.d.ts.map +1 -1
- package/dist/menu/createMenu.d.ts +1 -0
- package/dist/menu/createMenu.d.ts.map +1 -1
- package/dist/meter/createMeter.d.ts.map +1 -1
- package/dist/numberfield/createNumberField.d.ts +18 -0
- package/dist/numberfield/createNumberField.d.ts.map +1 -1
- package/dist/overlays/createModal.d.ts +16 -0
- package/dist/overlays/createModal.d.ts.map +1 -1
- package/dist/overlays/createOverlay.d.ts.map +1 -1
- package/dist/overlays/index.d.ts +1 -1
- package/dist/overlays/index.d.ts.map +1 -1
- package/dist/popover/createOverlayPosition.d.ts.map +1 -1
- package/dist/popover/createPopover.d.ts.map +1 -1
- package/dist/progress/createProgressBar.d.ts.map +1 -1
- package/dist/radio/createRadioGroup.d.ts +2 -2
- package/dist/radio/createRadioGroup.d.ts.map +1 -1
- package/dist/searchfield/createSearchField.d.ts.map +1 -1
- package/dist/select/createHiddenSelect.d.ts.map +1 -1
- package/dist/select/createSelect.d.ts.map +1 -1
- package/dist/slider/createSlider.d.ts.map +1 -1
- package/dist/table/createTable.d.ts.map +1 -1
- package/dist/tabs/createTabs.d.ts +1 -1
- package/dist/tabs/createTabs.d.ts.map +1 -1
- package/dist/tag/createTag.d.ts.map +1 -1
- package/dist/tag/createTagGroup.d.ts.map +1 -1
- package/dist/toast/createToast.d.ts +4 -0
- package/dist/toast/createToast.d.ts.map +1 -1
- package/dist/toast/createToastRegion.d.ts.map +1 -1
- package/dist/toolbar/createToolbar.d.ts.map +1 -1
- package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
- package/dist/tree/createTree.d.ts.map +1 -1
- package/dist/tree/createTreeItem.d.ts.map +1 -1
- package/dist/tree/types.d.ts +4 -0
- package/dist/tree/types.d.ts.map +1 -1
- package/dist/utils/env.d.ts +1 -1
- package/dist/utils/env.d.ts.map +1 -1
- package/dist/utils/platform.d.ts.map +1 -1
- package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
- package/package.json +8 -6
- package/src/actiongroup/createActionGroup.ts +324 -0
- package/src/actiongroup/index.ts +8 -0
- package/src/autocomplete/createAutocomplete.ts +32 -9
- package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
- package/src/button/createButton.ts +1 -1
- package/src/button/createToggleButtonGroup.ts +128 -0
- package/src/button/index.ts +9 -0
- package/src/calendar/createCalendarCell.ts +6 -4
- package/src/calendar/createCalendarGrid.ts +27 -18
- package/src/calendar/createRangeCalendarCell.ts +26 -9
- package/src/checkbox/createCheckboxGroup.ts +21 -4
- package/src/collections/index.ts +242 -0
- package/src/color/createColorArea.ts +380 -314
- package/src/color/createColorField.ts +137 -137
- package/src/color/createColorSlider.ts +286 -197
- package/src/color/createColorSwatch.ts +40 -40
- package/src/color/createColorWheel.ts +218 -208
- package/src/color/index.ts +24 -24
- package/src/color/types.ts +116 -116
- package/src/combobox/createComboBox.ts +670 -647
- package/src/combobox/index.ts +6 -6
- package/src/datepicker/createDatePicker.ts +54 -16
- package/src/datepicker/createDateRangePicker.ts +246 -0
- package/src/datepicker/createDateSegment.ts +185 -31
- package/src/datepicker/createTimeSegment.ts +370 -0
- package/src/datepicker/index.ts +14 -0
- package/src/dialog/createDialog.ts +120 -120
- package/src/dialog/index.ts +2 -2
- package/src/dialog/types.ts +19 -19
- package/src/disclosure/createDisclosureGroup.ts +5 -2
- package/src/dnd/createDrag.ts +224 -209
- package/src/dnd/createDraggableCollection.ts +96 -63
- package/src/dnd/createDraggableItem.ts +259 -243
- package/src/dnd/createDrop.ts +322 -321
- package/src/dnd/createDroppableCollection.ts +682 -293
- package/src/dnd/createDroppableItem.ts +215 -213
- package/src/dnd/index.ts +55 -47
- package/src/dnd/types.ts +89 -89
- package/src/dnd/utils.ts +294 -294
- package/src/focus/createAutoFocus.ts +321 -321
- package/src/focus/createFocusRestore.ts +313 -313
- package/src/focus/createVirtualFocus.ts +396 -396
- package/src/form/createFormValidation.ts +224 -224
- package/src/form/index.ts +11 -11
- package/src/grid/createGrid.ts +3 -1
- package/src/gridlist/createGridList.ts +16 -0
- package/src/gridlist/createGridListItem.ts +1 -1
- package/src/i18n/NumberFormatter.ts +266 -266
- package/src/i18n/createCollator.ts +79 -79
- package/src/i18n/createDateFormatter.ts +83 -83
- package/src/i18n/createFilter.ts +131 -131
- package/src/i18n/createNumberFormatter.ts +52 -52
- package/src/i18n/index.ts +40 -40
- package/src/i18n/locale.tsx +188 -188
- package/src/i18n/utils.ts +99 -99
- package/src/index.ts +51 -0
- package/src/interactions/createFocus.ts +6 -5
- package/src/interactions/createFocusWithin.ts +6 -5
- package/src/interactions/createLongPress.ts +174 -174
- package/src/interactions/createMove.ts +289 -289
- package/src/interactions/createPress.ts +5 -5
- package/src/landmark/createLandmark.ts +377 -377
- package/src/landmark/index.ts +8 -8
- package/src/link/createLink.ts +23 -8
- package/src/listbox/createListBox.ts +308 -269
- package/src/listbox/createOption.ts +162 -151
- package/src/listbox/index.ts +12 -12
- package/src/live-announcer/announce.ts +322 -322
- package/src/live-announcer/index.ts +9 -9
- package/src/menu/createMenu.ts +405 -396
- package/src/menu/createMenuItem.ts +149 -149
- package/src/menu/createMenuTrigger.ts +88 -88
- package/src/menu/index.ts +18 -18
- package/src/meter/createMeter.ts +1 -6
- package/src/numberfield/createNumberField.ts +311 -268
- package/src/numberfield/index.ts +5 -5
- package/src/overlays/ariaHideOutside.ts +219 -219
- package/src/overlays/createInteractOutside.ts +149 -149
- package/src/overlays/createModal.tsx +238 -202
- package/src/overlays/createOverlay.ts +165 -155
- package/src/overlays/createOverlayTrigger.ts +85 -85
- package/src/overlays/createPreventScroll.ts +266 -266
- package/src/overlays/index.ts +48 -44
- package/src/popover/calculatePosition.ts +6 -6
- package/src/popover/createOverlayPosition.ts +7 -4
- package/src/popover/createPopover.ts +21 -7
- package/src/progress/createProgressBar.ts +6 -1
- package/src/radio/createRadioGroup.ts +88 -14
- package/src/searchfield/createSearchField.ts +241 -186
- package/src/searchfield/index.ts +2 -2
- package/src/select/createHiddenSelect.tsx +263 -236
- package/src/select/createSelect.ts +373 -395
- package/src/select/index.ts +14 -14
- package/src/slider/createSlider.ts +364 -349
- package/src/slider/index.ts +2 -2
- package/src/ssr/index.tsx +370 -370
- package/src/table/createTable.ts +3 -1
- package/src/table/createTableColumnHeader.ts +1 -1
- package/src/table/createTableRow.ts +1 -1
- package/src/tabs/createTabs.ts +80 -51
- package/src/tag/createTag.ts +135 -6
- package/src/tag/createTagGroup.ts +7 -2
- package/src/toast/createToast.ts +8 -2
- package/src/toast/createToastRegion.ts +0 -1
- package/src/toolbar/createToolbar.ts +75 -1
- package/src/tooltip/createTooltip.ts +79 -79
- package/src/tooltip/createTooltipTrigger.ts +226 -222
- package/src/tooltip/index.ts +6 -6
- package/src/tree/createTree.ts +261 -246
- package/src/tree/createTreeItem.ts +282 -233
- package/src/tree/createTreeSelectionCheckbox.ts +68 -68
- package/src/tree/index.ts +16 -16
- package/src/tree/types.ts +91 -87
- package/src/utils/env.ts +55 -54
- package/src/utils/platform.ts +16 -6
- package/src/visually-hidden/createVisuallyHidden.ts +139 -124
- package/src/visually-hidden/index.ts +6 -6
|
@@ -1,266 +1,266 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* NumberFormatter for solidaria
|
|
3
|
-
*
|
|
4
|
-
* A wrapper around Intl.NumberFormat with caching and polyfills.
|
|
5
|
-
*
|
|
6
|
-
* Port of @internationalized/number NumberFormatter.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ============================================
|
|
10
|
-
// FEATURE DETECTION
|
|
11
|
-
// ============================================
|
|
12
|
-
|
|
13
|
-
let supportsSignDisplay = false;
|
|
14
|
-
try {
|
|
15
|
-
supportsSignDisplay =
|
|
16
|
-
new Intl.NumberFormat('de-DE', { signDisplay: 'exceptZero' }).resolvedOptions()
|
|
17
|
-
.signDisplay === 'exceptZero';
|
|
18
|
-
} catch {
|
|
19
|
-
// Not supported
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let supportsUnit = false;
|
|
23
|
-
try {
|
|
24
|
-
supportsUnit =
|
|
25
|
-
new Intl.NumberFormat('de-DE', { style: 'unit', unit: 'degree' }).resolvedOptions()
|
|
26
|
-
.style === 'unit';
|
|
27
|
-
} catch {
|
|
28
|
-
// Not supported
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ============================================
|
|
32
|
-
// POLYFILLS
|
|
33
|
-
// ============================================
|
|
34
|
-
|
|
35
|
-
// Polyfill for units since Safari doesn't support them yet.
|
|
36
|
-
// Currently only polyfilling the unit degree in narrow format.
|
|
37
|
-
const UNITS: Record<string, Record<string, Record<string, string>>> = {
|
|
38
|
-
degree: {
|
|
39
|
-
narrow: {
|
|
40
|
-
default: '°',
|
|
41
|
-
'ja-JP': ' 度',
|
|
42
|
-
'zh-TW': '度',
|
|
43
|
-
'sl-SI': ' °',
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// ============================================
|
|
49
|
-
// TYPES
|
|
50
|
-
// ============================================
|
|
51
|
-
|
|
52
|
-
export interface NumberFormatOptions extends Intl.NumberFormatOptions {
|
|
53
|
-
/** Overrides default numbering system for the current locale. */
|
|
54
|
-
numberingSystem?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface NumberRangeFormatPart extends Intl.NumberFormatPart {
|
|
58
|
-
source: 'startRange' | 'endRange' | 'shared';
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ============================================
|
|
62
|
-
// CACHE
|
|
63
|
-
// ============================================
|
|
64
|
-
|
|
65
|
-
const formatterCache = new Map<string, Intl.NumberFormat>();
|
|
66
|
-
|
|
67
|
-
function getCachedNumberFormatter(
|
|
68
|
-
locale: string,
|
|
69
|
-
options: NumberFormatOptions = {}
|
|
70
|
-
): Intl.NumberFormat {
|
|
71
|
-
let processedLocale = locale;
|
|
72
|
-
const { numberingSystem } = options;
|
|
73
|
-
|
|
74
|
-
if (numberingSystem && !processedLocale.includes('-nu-')) {
|
|
75
|
-
if (!processedLocale.includes('-u-')) {
|
|
76
|
-
processedLocale += '-u-';
|
|
77
|
-
}
|
|
78
|
-
processedLocale += `-nu-${numberingSystem}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let processedOptions = options;
|
|
82
|
-
if (options.style === 'unit' && !supportsUnit) {
|
|
83
|
-
const { unit, unitDisplay = 'short' } = options;
|
|
84
|
-
if (!unit) {
|
|
85
|
-
throw new Error('unit option must be provided with style: "unit"');
|
|
86
|
-
}
|
|
87
|
-
if (!UNITS[unit]?.[unitDisplay]) {
|
|
88
|
-
throw new Error(`Unsupported unit ${unit} with unitDisplay = ${unitDisplay}`);
|
|
89
|
-
}
|
|
90
|
-
processedOptions = { ...options, style: 'decimal' };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const cacheKey =
|
|
94
|
-
processedLocale +
|
|
95
|
-
(processedOptions
|
|
96
|
-
? Object.entries(processedOptions)
|
|
97
|
-
.sort((a, b) => (a[0] < b[0] ? -1 : 1))
|
|
98
|
-
.join()
|
|
99
|
-
: '');
|
|
100
|
-
|
|
101
|
-
if (formatterCache.has(cacheKey)) {
|
|
102
|
-
return formatterCache.get(cacheKey)!;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const numberFormatter = new Intl.NumberFormat(processedLocale, processedOptions);
|
|
106
|
-
formatterCache.set(cacheKey, numberFormatter);
|
|
107
|
-
return numberFormatter;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ============================================
|
|
111
|
-
// SIGN DISPLAY POLYFILL
|
|
112
|
-
// ============================================
|
|
113
|
-
|
|
114
|
-
function numberFormatSignDisplayPolyfill(
|
|
115
|
-
numberFormat: Intl.NumberFormat,
|
|
116
|
-
signDisplay: string,
|
|
117
|
-
num: number
|
|
118
|
-
): string {
|
|
119
|
-
if (signDisplay === 'auto') {
|
|
120
|
-
return numberFormat.format(num);
|
|
121
|
-
} else if (signDisplay === 'never') {
|
|
122
|
-
return numberFormat.format(Math.abs(num));
|
|
123
|
-
} else {
|
|
124
|
-
let needsPositiveSign = false;
|
|
125
|
-
let processedNum = num;
|
|
126
|
-
|
|
127
|
-
if (signDisplay === 'always') {
|
|
128
|
-
needsPositiveSign = num > 0 || Object.is(num, 0);
|
|
129
|
-
} else if (signDisplay === 'exceptZero') {
|
|
130
|
-
if (Object.is(num, -0) || Object.is(num, 0)) {
|
|
131
|
-
processedNum = Math.abs(num);
|
|
132
|
-
} else {
|
|
133
|
-
needsPositiveSign = num > 0;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (needsPositiveSign) {
|
|
138
|
-
const negative = numberFormat.format(-processedNum);
|
|
139
|
-
const noSign = numberFormat.format(processedNum);
|
|
140
|
-
// Ignore RTL/LTR marker characters
|
|
141
|
-
const minus = negative.replace(noSign, '').replace(/\u200e|\u061C/, '');
|
|
142
|
-
if ([...minus].length !== 1) {
|
|
143
|
-
console.warn(
|
|
144
|
-
'solidaria i18n polyfill for NumberFormat signDisplay: Unsupported case'
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
const positive = negative
|
|
148
|
-
.replace(noSign, '!!!')
|
|
149
|
-
.replace(minus, '+')
|
|
150
|
-
.replace('!!!', noSign);
|
|
151
|
-
return positive;
|
|
152
|
-
} else {
|
|
153
|
-
return numberFormat.format(processedNum);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ============================================
|
|
159
|
-
// NUMBER FORMATTER CLASS
|
|
160
|
-
// ============================================
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* A wrapper around Intl.NumberFormat providing additional options, polyfills, and caching.
|
|
164
|
-
*
|
|
165
|
-
* @example
|
|
166
|
-
* ```ts
|
|
167
|
-
* const formatter = new NumberFormatter('en-US', {
|
|
168
|
-
* style: 'currency',
|
|
169
|
-
* currency: 'USD',
|
|
170
|
-
* });
|
|
171
|
-
* formatter.format(1234.56); // '$1,234.56'
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
export class NumberFormatter implements Intl.NumberFormat {
|
|
175
|
-
private numberFormatter: Intl.NumberFormat;
|
|
176
|
-
private options: NumberFormatOptions;
|
|
177
|
-
|
|
178
|
-
constructor(locale: string, options: NumberFormatOptions = {}) {
|
|
179
|
-
this.numberFormatter = getCachedNumberFormatter(locale, options);
|
|
180
|
-
this.options = options;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/** Formats a number value as a string, according to the locale and options. */
|
|
184
|
-
format(value: number): string {
|
|
185
|
-
let res = '';
|
|
186
|
-
|
|
187
|
-
if (!supportsSignDisplay && this.options.signDisplay != null) {
|
|
188
|
-
res = numberFormatSignDisplayPolyfill(
|
|
189
|
-
this.numberFormatter,
|
|
190
|
-
this.options.signDisplay,
|
|
191
|
-
value
|
|
192
|
-
);
|
|
193
|
-
} else {
|
|
194
|
-
res = this.numberFormatter.format(value);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (this.options.style === 'unit' && !supportsUnit) {
|
|
198
|
-
const { unit, unitDisplay = 'short', locale } = this.resolvedOptions();
|
|
199
|
-
if (!unit) {
|
|
200
|
-
return res;
|
|
201
|
-
}
|
|
202
|
-
const values = UNITS[unit]?.[unitDisplay];
|
|
203
|
-
res += values[locale] || values.default;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return res;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/** Formats a number to an array of parts. */
|
|
210
|
-
formatToParts(value: number): Intl.NumberFormatPart[] {
|
|
211
|
-
return this.numberFormatter.formatToParts(value);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/** Formats a number range as a string. */
|
|
215
|
-
formatRange(start: number, end: number): string {
|
|
216
|
-
if (typeof this.numberFormatter.formatRange === 'function') {
|
|
217
|
-
return this.numberFormatter.formatRange(start, end);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (end < start) {
|
|
221
|
-
throw new RangeError('End date must be >= start date');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Fallback for old browsers
|
|
225
|
-
return `${this.format(start)} – ${this.format(end)}`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/** Formats a number range as an array of parts. */
|
|
229
|
-
formatRangeToParts(start: number, end: number): NumberRangeFormatPart[] {
|
|
230
|
-
if (typeof this.numberFormatter.formatRangeToParts === 'function') {
|
|
231
|
-
return this.numberFormatter.formatRangeToParts(start, end);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (end < start) {
|
|
235
|
-
throw new RangeError('End date must be >= start date');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const startParts = this.numberFormatter.formatToParts(start);
|
|
239
|
-
const endParts = this.numberFormatter.formatToParts(end);
|
|
240
|
-
return [
|
|
241
|
-
...startParts.map((p) => ({ ...p, source: 'startRange' as const })),
|
|
242
|
-
{ type: 'literal' as const, value: ' – ', source: 'shared' as const },
|
|
243
|
-
...endParts.map((p) => ({ ...p, source: 'endRange' as const })),
|
|
244
|
-
];
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/** Returns the resolved formatting options. */
|
|
248
|
-
resolvedOptions(): Intl.ResolvedNumberFormatOptions {
|
|
249
|
-
let options = this.numberFormatter.resolvedOptions();
|
|
250
|
-
|
|
251
|
-
if (!supportsSignDisplay && this.options.signDisplay != null) {
|
|
252
|
-
options = { ...options, signDisplay: this.options.signDisplay };
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (!supportsUnit && this.options.style === 'unit') {
|
|
256
|
-
options = {
|
|
257
|
-
...options,
|
|
258
|
-
style: 'unit',
|
|
259
|
-
unit: this.options.unit,
|
|
260
|
-
unitDisplay: this.options.unitDisplay,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
return options;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* NumberFormatter for solidaria
|
|
3
|
+
*
|
|
4
|
+
* A wrapper around Intl.NumberFormat with caching and polyfills.
|
|
5
|
+
*
|
|
6
|
+
* Port of @internationalized/number NumberFormatter.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// FEATURE DETECTION
|
|
11
|
+
// ============================================
|
|
12
|
+
|
|
13
|
+
let supportsSignDisplay = false;
|
|
14
|
+
try {
|
|
15
|
+
supportsSignDisplay =
|
|
16
|
+
new Intl.NumberFormat('de-DE', { signDisplay: 'exceptZero' }).resolvedOptions()
|
|
17
|
+
.signDisplay === 'exceptZero';
|
|
18
|
+
} catch {
|
|
19
|
+
// Not supported
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let supportsUnit = false;
|
|
23
|
+
try {
|
|
24
|
+
supportsUnit =
|
|
25
|
+
new Intl.NumberFormat('de-DE', { style: 'unit', unit: 'degree' }).resolvedOptions()
|
|
26
|
+
.style === 'unit';
|
|
27
|
+
} catch {
|
|
28
|
+
// Not supported
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================
|
|
32
|
+
// POLYFILLS
|
|
33
|
+
// ============================================
|
|
34
|
+
|
|
35
|
+
// Polyfill for units since Safari doesn't support them yet.
|
|
36
|
+
// Currently only polyfilling the unit degree in narrow format.
|
|
37
|
+
const UNITS: Record<string, Record<string, Record<string, string>>> = {
|
|
38
|
+
degree: {
|
|
39
|
+
narrow: {
|
|
40
|
+
default: '°',
|
|
41
|
+
'ja-JP': ' 度',
|
|
42
|
+
'zh-TW': '度',
|
|
43
|
+
'sl-SI': ' °',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// ============================================
|
|
49
|
+
// TYPES
|
|
50
|
+
// ============================================
|
|
51
|
+
|
|
52
|
+
export interface NumberFormatOptions extends Intl.NumberFormatOptions {
|
|
53
|
+
/** Overrides default numbering system for the current locale. */
|
|
54
|
+
numberingSystem?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface NumberRangeFormatPart extends Intl.NumberFormatPart {
|
|
58
|
+
source: 'startRange' | 'endRange' | 'shared';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// CACHE
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
const formatterCache = new Map<string, Intl.NumberFormat>();
|
|
66
|
+
|
|
67
|
+
function getCachedNumberFormatter(
|
|
68
|
+
locale: string,
|
|
69
|
+
options: NumberFormatOptions = {}
|
|
70
|
+
): Intl.NumberFormat {
|
|
71
|
+
let processedLocale = locale;
|
|
72
|
+
const { numberingSystem } = options;
|
|
73
|
+
|
|
74
|
+
if (numberingSystem && !processedLocale.includes('-nu-')) {
|
|
75
|
+
if (!processedLocale.includes('-u-')) {
|
|
76
|
+
processedLocale += '-u-';
|
|
77
|
+
}
|
|
78
|
+
processedLocale += `-nu-${numberingSystem}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let processedOptions = options;
|
|
82
|
+
if (options.style === 'unit' && !supportsUnit) {
|
|
83
|
+
const { unit, unitDisplay = 'short' } = options;
|
|
84
|
+
if (!unit) {
|
|
85
|
+
throw new Error('unit option must be provided with style: "unit"');
|
|
86
|
+
}
|
|
87
|
+
if (!UNITS[unit]?.[unitDisplay]) {
|
|
88
|
+
throw new Error(`Unsupported unit ${unit} with unitDisplay = ${unitDisplay}`);
|
|
89
|
+
}
|
|
90
|
+
processedOptions = { ...options, style: 'decimal' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const cacheKey =
|
|
94
|
+
processedLocale +
|
|
95
|
+
(processedOptions
|
|
96
|
+
? Object.entries(processedOptions)
|
|
97
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : 1))
|
|
98
|
+
.join()
|
|
99
|
+
: '');
|
|
100
|
+
|
|
101
|
+
if (formatterCache.has(cacheKey)) {
|
|
102
|
+
return formatterCache.get(cacheKey)!;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const numberFormatter = new Intl.NumberFormat(processedLocale, processedOptions);
|
|
106
|
+
formatterCache.set(cacheKey, numberFormatter);
|
|
107
|
+
return numberFormatter;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ============================================
|
|
111
|
+
// SIGN DISPLAY POLYFILL
|
|
112
|
+
// ============================================
|
|
113
|
+
|
|
114
|
+
function numberFormatSignDisplayPolyfill(
|
|
115
|
+
numberFormat: Intl.NumberFormat,
|
|
116
|
+
signDisplay: string,
|
|
117
|
+
num: number
|
|
118
|
+
): string {
|
|
119
|
+
if (signDisplay === 'auto') {
|
|
120
|
+
return numberFormat.format(num);
|
|
121
|
+
} else if (signDisplay === 'never') {
|
|
122
|
+
return numberFormat.format(Math.abs(num));
|
|
123
|
+
} else {
|
|
124
|
+
let needsPositiveSign = false;
|
|
125
|
+
let processedNum = num;
|
|
126
|
+
|
|
127
|
+
if (signDisplay === 'always') {
|
|
128
|
+
needsPositiveSign = num > 0 || Object.is(num, 0);
|
|
129
|
+
} else if (signDisplay === 'exceptZero') {
|
|
130
|
+
if (Object.is(num, -0) || Object.is(num, 0)) {
|
|
131
|
+
processedNum = Math.abs(num);
|
|
132
|
+
} else {
|
|
133
|
+
needsPositiveSign = num > 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (needsPositiveSign) {
|
|
138
|
+
const negative = numberFormat.format(-processedNum);
|
|
139
|
+
const noSign = numberFormat.format(processedNum);
|
|
140
|
+
// Ignore RTL/LTR marker characters
|
|
141
|
+
const minus = negative.replace(noSign, '').replace(/\u200e|\u061C/, '');
|
|
142
|
+
if ([...minus].length !== 1) {
|
|
143
|
+
console.warn(
|
|
144
|
+
'solidaria i18n polyfill for NumberFormat signDisplay: Unsupported case'
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const positive = negative
|
|
148
|
+
.replace(noSign, '!!!')
|
|
149
|
+
.replace(minus, '+')
|
|
150
|
+
.replace('!!!', noSign);
|
|
151
|
+
return positive;
|
|
152
|
+
} else {
|
|
153
|
+
return numberFormat.format(processedNum);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ============================================
|
|
159
|
+
// NUMBER FORMATTER CLASS
|
|
160
|
+
// ============================================
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A wrapper around Intl.NumberFormat providing additional options, polyfills, and caching.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* const formatter = new NumberFormatter('en-US', {
|
|
168
|
+
* style: 'currency',
|
|
169
|
+
* currency: 'USD',
|
|
170
|
+
* });
|
|
171
|
+
* formatter.format(1234.56); // '$1,234.56'
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
export class NumberFormatter implements Intl.NumberFormat {
|
|
175
|
+
private numberFormatter: Intl.NumberFormat;
|
|
176
|
+
private options: NumberFormatOptions;
|
|
177
|
+
|
|
178
|
+
constructor(locale: string, options: NumberFormatOptions = {}) {
|
|
179
|
+
this.numberFormatter = getCachedNumberFormatter(locale, options);
|
|
180
|
+
this.options = options;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Formats a number value as a string, according to the locale and options. */
|
|
184
|
+
format(value: number): string {
|
|
185
|
+
let res = '';
|
|
186
|
+
|
|
187
|
+
if (!supportsSignDisplay && this.options.signDisplay != null) {
|
|
188
|
+
res = numberFormatSignDisplayPolyfill(
|
|
189
|
+
this.numberFormatter,
|
|
190
|
+
this.options.signDisplay,
|
|
191
|
+
value
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
res = this.numberFormatter.format(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (this.options.style === 'unit' && !supportsUnit) {
|
|
198
|
+
const { unit, unitDisplay = 'short', locale } = this.resolvedOptions();
|
|
199
|
+
if (!unit) {
|
|
200
|
+
return res;
|
|
201
|
+
}
|
|
202
|
+
const values = UNITS[unit]?.[unitDisplay];
|
|
203
|
+
res += values[locale] || values.default;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return res;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Formats a number to an array of parts. */
|
|
210
|
+
formatToParts(value: number): Intl.NumberFormatPart[] {
|
|
211
|
+
return this.numberFormatter.formatToParts(value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Formats a number range as a string. */
|
|
215
|
+
formatRange(start: number, end: number): string {
|
|
216
|
+
if (typeof this.numberFormatter.formatRange === 'function') {
|
|
217
|
+
return this.numberFormatter.formatRange(start, end);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (end < start) {
|
|
221
|
+
throw new RangeError('End date must be >= start date');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Fallback for old browsers
|
|
225
|
+
return `${this.format(start)} – ${this.format(end)}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Formats a number range as an array of parts. */
|
|
229
|
+
formatRangeToParts(start: number, end: number): NumberRangeFormatPart[] {
|
|
230
|
+
if (typeof this.numberFormatter.formatRangeToParts === 'function') {
|
|
231
|
+
return this.numberFormatter.formatRangeToParts(start, end);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (end < start) {
|
|
235
|
+
throw new RangeError('End date must be >= start date');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const startParts = this.numberFormatter.formatToParts(start);
|
|
239
|
+
const endParts = this.numberFormatter.formatToParts(end);
|
|
240
|
+
return [
|
|
241
|
+
...startParts.map((p) => ({ ...p, source: 'startRange' as const })),
|
|
242
|
+
{ type: 'literal' as const, value: ' – ', source: 'shared' as const },
|
|
243
|
+
...endParts.map((p) => ({ ...p, source: 'endRange' as const })),
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Returns the resolved formatting options. */
|
|
248
|
+
resolvedOptions(): Intl.ResolvedNumberFormatOptions {
|
|
249
|
+
let options = this.numberFormatter.resolvedOptions();
|
|
250
|
+
|
|
251
|
+
if (!supportsSignDisplay && this.options.signDisplay != null) {
|
|
252
|
+
options = { ...options, signDisplay: this.options.signDisplay };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!supportsUnit && this.options.style === 'unit') {
|
|
256
|
+
options = {
|
|
257
|
+
...options,
|
|
258
|
+
style: 'unit',
|
|
259
|
+
unit: this.options.unit,
|
|
260
|
+
unitDisplay: this.options.unitDisplay,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return options;
|
|
265
|
+
}
|
|
266
|
+
}
|