@limetech/lime-elements 40.21.1 → 40.22.0
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/CHANGELOG.md +12 -0
- package/dist/cjs/language-s0bKUCvO.js +58 -0
- package/dist/cjs/lime-elements.cjs.js +1 -1
- package/dist/cjs/limel-action-bar_3.cjs.entry.js +2 -2
- package/dist/cjs/limel-ai-avatar.cjs.entry.js +2 -2
- package/dist/cjs/limel-badge_7.cjs.entry.js +2 -2
- package/dist/cjs/limel-callout.cjs.entry.js +2 -2
- package/dist/cjs/limel-chart.cjs.entry.js +524 -76
- package/dist/cjs/limel-chip-set.cjs.entry.js +2 -2
- package/dist/cjs/limel-chip_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-code-diff.cjs.entry.js +2 -2
- package/dist/cjs/limel-code-editor.cjs.entry.js +2 -2
- package/dist/cjs/limel-collapsible-section.cjs.entry.js +2 -2
- package/dist/cjs/limel-date-picker.cjs.entry.js +1 -1
- package/dist/cjs/limel-dock.cjs.entry.js +2 -2
- package/dist/cjs/limel-drag-handle_2.cjs.entry.js +2 -2
- package/dist/cjs/limel-email-viewer.cjs.entry.js +2 -2
- package/dist/cjs/limel-file-viewer.cjs.entry.js +2 -2
- package/dist/cjs/limel-file.cjs.entry.js +2 -2
- package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +2 -2
- package/dist/cjs/limel-list-item.cjs.entry.js +2 -2
- package/dist/cjs/limel-pagination-jump.cjs.entry.js +2 -2
- package/dist/cjs/limel-pagination.cjs.entry.js +14 -26
- package/dist/cjs/limel-picker.cjs.entry.js +2 -2
- package/dist/cjs/limel-profile-picture.cjs.entry.js +2 -2
- package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +2 -2
- package/dist/cjs/limel-resize-handle.cjs.entry.js +2 -2
- package/dist/cjs/limel-side-panel.cjs.entry.js +2 -2
- package/dist/cjs/limel-slider.cjs.entry.js +2 -2
- package/dist/cjs/limel-snackbar.cjs.entry.js +2 -2
- package/dist/cjs/limel-table.cjs.entry.js +2 -2
- package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +2 -2
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/{translations-CqJEWcfw.js → translations-JLP5icQ9.js} +1 -1
- package/dist/collection/components/chart/chart.js +116 -45
- package/dist/collection/components/chart/format.js +302 -0
- package/dist/collection/components/chart/options.js +26 -14
- package/dist/collection/components/chart/scale.js +132 -19
- package/dist/collection/components/pagination/pagination.js +13 -25
- package/dist/collection/util/language.js +29 -0
- package/dist/esm/language-VamkUr90.js +55 -0
- package/dist/esm/lime-elements.js +1 -1
- package/dist/esm/limel-action-bar_3.entry.js +2 -2
- package/dist/esm/limel-ai-avatar.entry.js +2 -2
- package/dist/esm/limel-badge_7.entry.js +2 -2
- package/dist/esm/limel-callout.entry.js +2 -2
- package/dist/esm/limel-chart.entry.js +524 -76
- package/dist/esm/limel-chip-set.entry.js +2 -2
- package/dist/esm/limel-chip_2.entry.js +2 -2
- package/dist/esm/limel-code-diff.entry.js +2 -2
- package/dist/esm/limel-code-editor.entry.js +2 -2
- package/dist/esm/limel-collapsible-section.entry.js +2 -2
- package/dist/esm/limel-date-picker.entry.js +1 -1
- package/dist/esm/limel-dock.entry.js +2 -2
- package/dist/esm/limel-drag-handle_2.entry.js +2 -2
- package/dist/esm/limel-email-viewer.entry.js +2 -2
- package/dist/esm/limel-file-viewer.entry.js +2 -2
- package/dist/esm/limel-file.entry.js +2 -2
- package/dist/esm/limel-flatpickr-adapter.entry.js +2 -2
- package/dist/esm/limel-list-item.entry.js +2 -2
- package/dist/esm/limel-pagination-jump.entry.js +2 -2
- package/dist/esm/limel-pagination.entry.js +14 -26
- package/dist/esm/limel-picker.entry.js +2 -2
- package/dist/esm/limel-profile-picture.entry.js +2 -2
- package/dist/esm/limel-prosemirror-adapter.entry.js +2 -2
- package/dist/esm/limel-resize-handle.entry.js +2 -2
- package/dist/esm/limel-side-panel.entry.js +2 -2
- package/dist/esm/limel-slider.entry.js +2 -2
- package/dist/esm/limel-snackbar.entry.js +2 -2
- package/dist/esm/limel-table.entry.js +2 -2
- package/dist/esm/limel-text-editor-link-menu.entry.js +2 -2
- package/dist/esm/loader.js +1 -1
- package/dist/esm/{translations-kct5Kybu.js → translations-D9ZGrMhe.js} +1 -1
- package/dist/lime-elements/lime-elements.esm.js +1 -1
- package/dist/lime-elements/{p-6e9eeb0a.entry.js → p-0a7cbdc3.entry.js} +1 -1
- package/dist/lime-elements/{p-d35d2859.entry.js → p-0f96e94d.entry.js} +1 -1
- package/dist/lime-elements/{p-a7be03e1.entry.js → p-1a0c5e45.entry.js} +1 -1
- package/dist/lime-elements/{p-5e408c27.entry.js → p-401d5c92.entry.js} +1 -1
- package/dist/lime-elements/{p-6d2d25b9.entry.js → p-430db3b9.entry.js} +1 -1
- package/dist/lime-elements/{p-a6c6a31b.entry.js → p-43b62587.entry.js} +1 -1
- package/dist/lime-elements/{p-958396b5.entry.js → p-48dccb83.entry.js} +1 -1
- package/dist/lime-elements/{p-814b4934.entry.js → p-5c918155.entry.js} +1 -1
- package/dist/lime-elements/{p-22e9e327.entry.js → p-66101ed6.entry.js} +1 -1
- package/dist/lime-elements/{p-d8559847.entry.js → p-7ab72b52.entry.js} +1 -1
- package/dist/lime-elements/{p-7af83877.entry.js → p-8d9a2a22.entry.js} +1 -1
- package/dist/lime-elements/{p-55fa19ae.entry.js → p-8de4f0f3.entry.js} +1 -1
- package/dist/lime-elements/p-8e80a884.entry.js +56 -0
- package/dist/lime-elements/{p-bffb5ed2.entry.js → p-99984c66.entry.js} +1 -1
- package/dist/lime-elements/{p-56e2fa48.entry.js → p-9d46f720.entry.js} +1 -1
- package/dist/lime-elements/{p-68bbb8dd.entry.js → p-9e2b426c.entry.js} +1 -1
- package/dist/lime-elements/{p-Dzp8dXTo.js → p-C9dBa_Im.js} +1 -1
- package/dist/lime-elements/p-VamkUr90.js +1 -0
- package/dist/lime-elements/{p-3ac39bd5.entry.js → p-b1c7033b.entry.js} +2 -2
- package/dist/lime-elements/{p-47fe76a8.entry.js → p-b5451350.entry.js} +1 -1
- package/dist/lime-elements/{p-9edd79a8.entry.js → p-c75f3b0d.entry.js} +1 -1
- package/dist/lime-elements/{p-4c080cd9.entry.js → p-ca4247c4.entry.js} +1 -1
- package/dist/lime-elements/{p-1a7d1d2b.entry.js → p-d53421af.entry.js} +1 -1
- package/dist/lime-elements/{p-6d9b89db.entry.js → p-d8901e39.entry.js} +1 -1
- package/dist/lime-elements/{p-933ffaa8.entry.js → p-d89553b3.entry.js} +1 -1
- package/dist/lime-elements/{p-496b2437.entry.js → p-d93b8ce0.entry.js} +1 -1
- package/dist/lime-elements/{p-23067836.entry.js → p-e97340c4.entry.js} +1 -1
- package/dist/lime-elements/{p-7b87e547.entry.js → p-ec0c113d.entry.js} +1 -1
- package/dist/lime-elements/{p-9f0575fd.entry.js → p-f197000b.entry.js} +4 -4
- package/dist/lime-elements/{p-07f3565b.entry.js → p-f6bc6658.entry.js} +1 -1
- package/dist/lime-elements/{p-7fcd48c6.entry.js → p-fa1aa440.entry.js} +1 -1
- package/dist/types/components/chart/chart.d.ts +32 -7
- package/dist/types/components/chart/chart.types.d.ts +15 -1
- package/dist/types/components/chart/format.d.ts +54 -0
- package/dist/types/components/chart/options.d.ts +3 -4
- package/dist/types/components/chart/scale.d.ts +39 -7
- package/dist/types/components/pagination/pagination.d.ts +6 -12
- package/dist/types/components.d.ts +36 -8
- package/dist/types/util/language.d.ts +19 -0
- package/package.json +1 -1
- package/dist/cjs/language-v-MMS6ym.js +0 -28
- package/dist/esm/language-ChagAaNf.js +0 -26
- package/dist/lime-elements/p-7d6a12fe.entry.js +0 -56
- package/dist/lime-elements/p-ChagAaNf.js +0 -1
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { getSupportedLocale } from "../../util/language";
|
|
2
|
+
import { getMaximumValue, getMinimumValue, isDate, } from "./scale";
|
|
3
|
+
const FALLBACK_LOCALE = 'en';
|
|
4
|
+
const PERCENT = 100;
|
|
5
|
+
/**
|
|
6
|
+
* The largest value on an axis from which its values are shortened,
|
|
7
|
+
* such as `1.2M` for 1 200 000.
|
|
8
|
+
*/
|
|
9
|
+
const SHORTEN_FROM = 10000;
|
|
10
|
+
/**
|
|
11
|
+
* The most decimals `Intl` writes in every browser.
|
|
12
|
+
*/
|
|
13
|
+
const MAX_DECIMALS = 20;
|
|
14
|
+
/**
|
|
15
|
+
* The most significant digits `Intl` writes.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_SIGNIFICANT_DIGITS = 21;
|
|
18
|
+
/**
|
|
19
|
+
* Up to three decimals, and six significant digits below one, so `0.0004`
|
|
20
|
+
* is not written as `0`.
|
|
21
|
+
*/
|
|
22
|
+
const NUMBERS = {
|
|
23
|
+
options: { maximumFractionDigits: 3 },
|
|
24
|
+
small: { maximumSignificantDigits: 6 },
|
|
25
|
+
smallBelow: 1,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Up to one decimal, and two significant digits below 1 %, so `0.0004` is
|
|
29
|
+
* written as `0.04%` rather than `0%`.
|
|
30
|
+
*/
|
|
31
|
+
const PERCENTAGES = {
|
|
32
|
+
options: { style: 'percent', maximumFractionDigits: 1 },
|
|
33
|
+
small: { style: 'percent', maximumSignificantDigits: 2 },
|
|
34
|
+
smallBelow: 0.01,
|
|
35
|
+
};
|
|
36
|
+
const SHARE_OPTIONS = {
|
|
37
|
+
style: 'percent',
|
|
38
|
+
minimumFractionDigits: 2,
|
|
39
|
+
maximumFractionDigits: 2,
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* The ticks of an axis of dates that are not known, whose dates are all
|
|
43
|
+
* written with their year.
|
|
44
|
+
*/
|
|
45
|
+
const UNKNOWN_DATE_TICKS = { values: [], unit: 'day' };
|
|
46
|
+
const locales = new Map();
|
|
47
|
+
const numberFormats = new Map();
|
|
48
|
+
const currencyCodes = new Map();
|
|
49
|
+
const dateFormats = new Map();
|
|
50
|
+
/**
|
|
51
|
+
* The item's value as text: its `formattedValue` when set, otherwise its
|
|
52
|
+
* value, range, or scatter point, written in the chart's language and
|
|
53
|
+
* value format.
|
|
54
|
+
* @param item - the item
|
|
55
|
+
* @param style - how the chart writes values
|
|
56
|
+
*/
|
|
57
|
+
export function formatValue(item, style) {
|
|
58
|
+
const { value, formattedValue } = item;
|
|
59
|
+
if (formattedValue) {
|
|
60
|
+
return formattedValue;
|
|
61
|
+
}
|
|
62
|
+
const write = getValueWriter(style);
|
|
63
|
+
if (!Array.isArray(value)) {
|
|
64
|
+
return write(value);
|
|
65
|
+
}
|
|
66
|
+
const [first, second] = value.map((part) => write(part));
|
|
67
|
+
if (style.type === 'scatter') {
|
|
68
|
+
return writePoint(first, second);
|
|
69
|
+
}
|
|
70
|
+
const [start, end] = [getMinimumValue(item), getMaximumValue(item)];
|
|
71
|
+
if (style.valueFormat === 'date' && isDate(start) && isDate(end)) {
|
|
72
|
+
return getDateFormat(style.language).formatRange(start, end);
|
|
73
|
+
}
|
|
74
|
+
return `${first} — ${second}`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Writes the values of an axis in the chart's language and value format,
|
|
78
|
+
* with as many decimals as the step between them has. Amounts leave out
|
|
79
|
+
* their currency, which the item values and tooltips show. On an axis
|
|
80
|
+
* that reaches 10 000 or more, every number is shortened, such as `1.2M`,
|
|
81
|
+
* so the values stay short and alike. Dates are written by what the axis
|
|
82
|
+
* steps through: days, months or years, with the year on the first date
|
|
83
|
+
* and on the first date of each new year.
|
|
84
|
+
* @param range - the range of the axis
|
|
85
|
+
* @param style - how the chart writes values
|
|
86
|
+
* @param dates - the dates an axis of dates marks
|
|
87
|
+
*/
|
|
88
|
+
export function getAxisValueFormatter(range, style, dates) {
|
|
89
|
+
const locale = getLocale(style.language);
|
|
90
|
+
if (style.valueFormat === 'date') {
|
|
91
|
+
return getDateAxisFormatter(locale, dates);
|
|
92
|
+
}
|
|
93
|
+
if (style.valueFormat === 'percent') {
|
|
94
|
+
const percentages = getNumberFormat(locale, {
|
|
95
|
+
style: 'percent',
|
|
96
|
+
maximumFractionDigits: countDecimals(range.increment * PERCENT),
|
|
97
|
+
});
|
|
98
|
+
return (value) => percentages.format(value);
|
|
99
|
+
}
|
|
100
|
+
const largest = Math.max(Math.abs(range.minValue), Math.abs(range.maxValue));
|
|
101
|
+
const numbers = getNumberFormat(locale, largest >= SHORTEN_FROM
|
|
102
|
+
? {
|
|
103
|
+
notation: 'compact',
|
|
104
|
+
maximumSignificantDigits: countSignificantDigits(largest, range.increment),
|
|
105
|
+
}
|
|
106
|
+
: { maximumFractionDigits: countDecimals(range.increment) });
|
|
107
|
+
return (value) => numbers.format(value);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* An item's share of the whole as a percentage with two decimals, such as
|
|
111
|
+
* `25.00%`, written in the chart's language.
|
|
112
|
+
* @param share - the share, in percent
|
|
113
|
+
* @param language - the language of the chart
|
|
114
|
+
*/
|
|
115
|
+
export function formatShare(share, language) {
|
|
116
|
+
return getNumberFormat(getLocale(language), SHARE_OPTIONS).format(share / PERCENT);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Whether `currency` can be written as one: a three-letter code such as
|
|
120
|
+
* `SEK`.
|
|
121
|
+
* @param currency - the currency of the chart
|
|
122
|
+
*/
|
|
123
|
+
export function isCurrencyCode(currency) {
|
|
124
|
+
let valid = currencyCodes.get(currency);
|
|
125
|
+
if (valid === undefined) {
|
|
126
|
+
valid = canWriteAmountsIn(currency);
|
|
127
|
+
currencyCodes.set(currency, valid);
|
|
128
|
+
}
|
|
129
|
+
return valid;
|
|
130
|
+
}
|
|
131
|
+
function canWriteAmountsIn(currency) {
|
|
132
|
+
try {
|
|
133
|
+
getNumberFormat(FALLBACK_LOCALE, { style: 'currency', currency });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch (_a) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Whether values can be written in `language`. When they cannot, they are
|
|
142
|
+
* written in English.
|
|
143
|
+
* @param language - the language of the chart
|
|
144
|
+
*/
|
|
145
|
+
export function canWriteIn(language) {
|
|
146
|
+
return findLocale(language) !== undefined;
|
|
147
|
+
}
|
|
148
|
+
function getValueWriter(style) {
|
|
149
|
+
if (style.valueFormat === 'date') {
|
|
150
|
+
const dates = getDateFormat(style.language);
|
|
151
|
+
return (value) => (isDate(value) ? dates.format(value) : `${value}`);
|
|
152
|
+
}
|
|
153
|
+
const numbers = getValueFormat(style);
|
|
154
|
+
return (value) => numbers.format(value);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Writes the dates on an axis by what it steps through: day and month for
|
|
158
|
+
* days and weeks, the month for months, and the year for years. The first
|
|
159
|
+
* date, and the first date of each new year, also get their year, except
|
|
160
|
+
* January among months, which is written as its year alone.
|
|
161
|
+
* @param locale - the locale of the chart
|
|
162
|
+
* @param ticks - the dates the axis marks
|
|
163
|
+
*/
|
|
164
|
+
function getDateAxisFormatter(locale, ticks = UNKNOWN_DATE_TICKS) {
|
|
165
|
+
const years = getDateTimeFormat(locale, { year: 'numeric' });
|
|
166
|
+
if (ticks.unit === 'year') {
|
|
167
|
+
return (value) => years.format(value);
|
|
168
|
+
}
|
|
169
|
+
const parts = ticks.unit === 'month'
|
|
170
|
+
? { month: 'short' }
|
|
171
|
+
: { day: 'numeric', month: 'short' };
|
|
172
|
+
const dates = getDateTimeFormat(locale, parts);
|
|
173
|
+
const datesWithYear = getDateTimeFormat(locale, Object.assign(Object.assign({}, parts), { year: 'numeric' }));
|
|
174
|
+
return (value) => {
|
|
175
|
+
if (!isFirstOfItsYear(value, ticks.values)) {
|
|
176
|
+
return dates.format(value);
|
|
177
|
+
}
|
|
178
|
+
return ticks.unit === 'month' && new Date(value).getMonth() === 0
|
|
179
|
+
? years.format(value)
|
|
180
|
+
: datesWithYear.format(value);
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Whether `value` is the first of `ticks` in its year. A date that is not
|
|
185
|
+
* one of them counts as the first, so that it gets its year.
|
|
186
|
+
* @param value - the date
|
|
187
|
+
* @param ticks - the dates an axis marks
|
|
188
|
+
*/
|
|
189
|
+
function isFirstOfItsYear(value, ticks) {
|
|
190
|
+
const index = ticks.indexOf(value);
|
|
191
|
+
return (index <= 0 ||
|
|
192
|
+
new Date(ticks[index - 1]).getFullYear() !==
|
|
193
|
+
new Date(value).getFullYear());
|
|
194
|
+
}
|
|
195
|
+
function getDateFormat(language) {
|
|
196
|
+
return getDateTimeFormat(getLocale(language), { dateStyle: 'medium' });
|
|
197
|
+
}
|
|
198
|
+
function getValueFormat(style) {
|
|
199
|
+
const locale = getLocale(style.language);
|
|
200
|
+
if (style.valueFormat === 'percent') {
|
|
201
|
+
return getNumberWriter(locale, PERCENTAGES);
|
|
202
|
+
}
|
|
203
|
+
if (style.valueFormat === 'currency' && isCurrencyCode(style.currency)) {
|
|
204
|
+
return getNumberFormat(locale, {
|
|
205
|
+
style: 'currency',
|
|
206
|
+
currency: style.currency,
|
|
207
|
+
trailingZeroDisplay: 'stripIfInteger',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return getNumberWriter(locale, NUMBERS);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* The two values of a scatter point, separated by a semicolon when either
|
|
214
|
+
* of them has a comma in it, such as `(1,5; 2,5)` in Swedish.
|
|
215
|
+
* @param first - the first value, as text
|
|
216
|
+
* @param second - the second value, as text
|
|
217
|
+
*/
|
|
218
|
+
function writePoint(first, second) {
|
|
219
|
+
const separator = `${first}${second}`.includes(',') ? '; ' : ', ';
|
|
220
|
+
return `(${first}${separator}${second})`;
|
|
221
|
+
}
|
|
222
|
+
function countDecimals(value) {
|
|
223
|
+
if (!Number.isFinite(value)) {
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
return Math.min(MAX_DECIMALS, Math.max(0, -getLowestDigit(value)));
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* How many significant digits the values of an axis need for every step
|
|
230
|
+
* between them to show, such as four for `1.025M` on an axis that steps
|
|
231
|
+
* by 25 000 up to 2 000 000.
|
|
232
|
+
* @param largest - the largest value on the axis, ignoring its sign
|
|
233
|
+
* @param increment - the step between the values
|
|
234
|
+
*/
|
|
235
|
+
function countSignificantDigits(largest, increment) {
|
|
236
|
+
if (!Number.isFinite(increment)) {
|
|
237
|
+
return MAX_SIGNIFICANT_DIGITS;
|
|
238
|
+
}
|
|
239
|
+
const digits = Math.floor(Math.log10(largest)) - getLowestDigit(increment);
|
|
240
|
+
return Math.min(MAX_SIGNIFICANT_DIGITS, Math.max(1, digits + 1));
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The power of ten of the last digit of `value` that is not zero, such as
|
|
244
|
+
* 3 for 25 000 and -2 for 0.25.
|
|
245
|
+
* @param value - a finite number
|
|
246
|
+
*/
|
|
247
|
+
function getLowestDigit(value) {
|
|
248
|
+
var _a, _b;
|
|
249
|
+
const [digits, exponent] = Number(value.toPrecision(12))
|
|
250
|
+
.toExponential()
|
|
251
|
+
.split('e', 2);
|
|
252
|
+
const decimals = (_b = (_a = digits.split('.', 2)[1]) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
|
|
253
|
+
return Number(exponent) - decimals;
|
|
254
|
+
}
|
|
255
|
+
function getNumberWriter(locale, style) {
|
|
256
|
+
const numbers = getNumberFormat(locale, style.options);
|
|
257
|
+
const small = getNumberFormat(locale, style.small);
|
|
258
|
+
return {
|
|
259
|
+
format: (value) => value !== 0 && Math.abs(value) < style.smallBelow
|
|
260
|
+
? small.format(value)
|
|
261
|
+
: numbers.format(value),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Making an `Intl.NumberFormat` costs far more than using one, and values
|
|
266
|
+
* are written on every render, so each one is made once.
|
|
267
|
+
* @param locale - the locale to write in
|
|
268
|
+
* @param options - how to write the numbers
|
|
269
|
+
*/
|
|
270
|
+
function getNumberFormat(locale, options) {
|
|
271
|
+
const key = `${locale} ${JSON.stringify(options)}`;
|
|
272
|
+
let format = numberFormats.get(key);
|
|
273
|
+
if (!format) {
|
|
274
|
+
format = new Intl.NumberFormat(locale, options);
|
|
275
|
+
numberFormats.set(key, format);
|
|
276
|
+
}
|
|
277
|
+
return format;
|
|
278
|
+
}
|
|
279
|
+
function getDateTimeFormat(locale, options) {
|
|
280
|
+
const key = `${locale} ${JSON.stringify(options)}`;
|
|
281
|
+
let format = dateFormats.get(key);
|
|
282
|
+
if (!format) {
|
|
283
|
+
format = new Intl.DateTimeFormat(locale, options);
|
|
284
|
+
dateFormats.set(key, format);
|
|
285
|
+
}
|
|
286
|
+
return format;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The locale to write values in: the chart's language when `Intl` can
|
|
290
|
+
* write in it, otherwise English.
|
|
291
|
+
* @param language - the language of the chart
|
|
292
|
+
*/
|
|
293
|
+
function getLocale(language) {
|
|
294
|
+
var _a;
|
|
295
|
+
return (_a = findLocale(language)) !== null && _a !== void 0 ? _a : FALLBACK_LOCALE;
|
|
296
|
+
}
|
|
297
|
+
function findLocale(language) {
|
|
298
|
+
if (!locales.has(language)) {
|
|
299
|
+
locales.set(language, getSupportedLocale(language));
|
|
300
|
+
}
|
|
301
|
+
return locales.get(language);
|
|
302
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { format } from "echarts/core";
|
|
2
|
-
import {
|
|
2
|
+
import { getAxisRange, getDateRange, getDateTicks, getMaximumValue, getMinimumValue, getScatterRanges, getXValue, getYValue, getShare, getValueSpan, getWholeRange, isPartOfWhole, isRangeItem, } from "./scale";
|
|
3
|
+
import { formatShare, formatValue, getAxisValueFormatter, } from "./format";
|
|
3
4
|
const DOT_SIZE = 8;
|
|
4
5
|
const LINE_DOT_SIZE = 6;
|
|
5
6
|
const CONNECTOR_WIDTH = 2;
|
|
6
7
|
const DIMMED_OPACITY = 0.4;
|
|
7
8
|
const AREA_OPACITY = 0.4;
|
|
8
9
|
const PADDING = 8;
|
|
9
|
-
const PERCENT_DECIMALS = 2;
|
|
10
10
|
const NPS_MIN = -100;
|
|
11
11
|
const NPS_MAX = 100;
|
|
12
12
|
const NPS_START_ANGLE = 200;
|
|
@@ -96,8 +96,8 @@ export function getItemLabel(settings, index) {
|
|
|
96
96
|
return item.text;
|
|
97
97
|
}
|
|
98
98
|
const whole = getWholeRange(settings.items, settings.maxValue);
|
|
99
|
-
const share = getShare(item, whole).
|
|
100
|
-
return `${item.text} (${share}
|
|
99
|
+
const share = formatShare(getShare(item, whole), settings.language);
|
|
100
|
+
return `${item.text} (${share})`;
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
103
|
* The tooltip of an item, as HTML: its label and its value.
|
|
@@ -109,7 +109,7 @@ export function getTooltip(settings, index) {
|
|
|
109
109
|
if (!item) {
|
|
110
110
|
return '';
|
|
111
111
|
}
|
|
112
|
-
return [getItemLabel(settings, index), formatValue(item, settings
|
|
112
|
+
return [getItemLabel(settings, index), formatValue(item, settings)]
|
|
113
113
|
.map((text) => format.encodeHTML(text))
|
|
114
114
|
.join('<br/>');
|
|
115
115
|
}
|
|
@@ -138,12 +138,17 @@ function drawSeries(settings, theme, itemSpace) {
|
|
|
138
138
|
}
|
|
139
139
|
function drawOnAxes(settings, theme, itemSpace) {
|
|
140
140
|
const { items, orientation } = settings;
|
|
141
|
-
const range =
|
|
141
|
+
const range = settings.valueFormat === 'date'
|
|
142
|
+
? getDateRange(items.flatMap((item) => [
|
|
143
|
+
getMinimumValue(item),
|
|
144
|
+
getMaximumValue(item),
|
|
145
|
+
]), settings.maxValue)
|
|
146
|
+
: getAxisRange(items, settings.maxValue, settings.axisIncrement);
|
|
142
147
|
const itemTextAxis = getItemTextAxis(settings);
|
|
143
148
|
const categoryAxis = Object.assign(Object.assign(Object.assign({}, getCategoryAxis(settings)), { axisLabel: Object.assign({ show: settings.displayItemText, color: theme.textColor }, (itemTextAxis &&
|
|
144
149
|
itemSpace &&
|
|
145
150
|
getItemTextLayout(itemTextAxis, itemSpace))), axisTick: hidden, axisLine: { lineStyle: { color: theme.axisLineColor } } }), getAxisName(settings, settings.itemsLabel, theme));
|
|
146
|
-
const valueAxis = getValueAxis(range, getAxisName(settings, settings.valuesLabel, theme), theme);
|
|
151
|
+
const valueAxis = getValueAxis(range, getAxisName(settings, settings.valuesLabel, theme), settings, theme);
|
|
147
152
|
const { series, seriesIndex } = getAxisSeries(settings, theme);
|
|
148
153
|
const option = {
|
|
149
154
|
grid: getGrid(theme),
|
|
@@ -306,10 +311,15 @@ function getRangeStarts(items) {
|
|
|
306
311
|
}
|
|
307
312
|
function drawScatter(settings, theme) {
|
|
308
313
|
const { items } = settings;
|
|
309
|
-
const ranges =
|
|
314
|
+
const ranges = settings.valueFormat === 'date'
|
|
315
|
+
? {
|
|
316
|
+
x: getDateRange(items.map((item) => getXValue(item))),
|
|
317
|
+
y: getDateRange(items.map((item) => getYValue(item))),
|
|
318
|
+
}
|
|
319
|
+
: getScatterRanges(items);
|
|
310
320
|
const landscape = settings.orientation === 'landscape';
|
|
311
|
-
const firstAxis = getValueAxis(ranges.x, getAxisName(settings, settings.itemsLabel, theme), theme);
|
|
312
|
-
const secondAxis = getValueAxis(ranges.y, getAxisName(settings, settings.valuesLabel, theme), theme);
|
|
321
|
+
const firstAxis = getValueAxis(ranges.x, getAxisName(settings, settings.itemsLabel, theme), settings, theme);
|
|
322
|
+
const secondAxis = getValueAxis(ranges.y, getAxisName(settings, settings.valuesLabel, theme), settings, theme);
|
|
313
323
|
const scatter = {
|
|
314
324
|
type: 'scatter',
|
|
315
325
|
symbolSize: theme.scatterItemSize,
|
|
@@ -531,8 +541,10 @@ function getGrid(theme) {
|
|
|
531
541
|
borderWidth: 0,
|
|
532
542
|
};
|
|
533
543
|
}
|
|
534
|
-
function getValueAxis(range, name, theme) {
|
|
535
|
-
|
|
544
|
+
function getValueAxis(range, name, settings, theme) {
|
|
545
|
+
const dates = settings.valueFormat === 'date' && getDateTicks(range);
|
|
546
|
+
const ticks = dates && { customValues: dates.values };
|
|
547
|
+
return Object.assign(Object.assign(Object.assign({ type: 'value', min: range.minValue, max: range.maxValue, interval: dates ? undefined : range.increment, axisLabel: Object.assign({ show: true, color: theme.textColor, formatter: getAxisValueFormatter(range, settings, dates || undefined) }, ticks) }, (ticks && { axisTick: ticks })), { axisLine: { lineStyle: { color: theme.axisLineColor } }, splitLine: { lineStyle: { color: theme.axisLineColor } } }), name);
|
|
536
548
|
}
|
|
537
549
|
function getAxisName(settings, name, theme) {
|
|
538
550
|
if (!settings.displayAxisLabels) {
|
|
@@ -545,7 +557,7 @@ function getAxisName(settings, name, theme) {
|
|
|
545
557
|
};
|
|
546
558
|
}
|
|
547
559
|
function getValueLabel(settings, theme) {
|
|
548
|
-
return Object.assign(Object.assign({ show: settings.displayItemValue, position: settings.orientation === 'landscape' ? 'top' : 'right' }, getLabelStyle(theme)), { formatter: ({ dataIndex }) => formatValue(settings.items[dataIndex], settings
|
|
560
|
+
return Object.assign(Object.assign({ show: settings.displayItemValue, position: settings.orientation === 'landscape' ? 'top' : 'right' }, getLabelStyle(theme)), { formatter: ({ dataIndex }) => formatValue(settings.items[dataIndex], settings) });
|
|
549
561
|
}
|
|
550
562
|
function getLabelStyle(theme) {
|
|
551
563
|
return { color: theme.textColor, textBorderWidth: 0 };
|
|
@@ -556,7 +568,7 @@ function getPointLabel(settings, item) {
|
|
|
556
568
|
parts.push(item.text);
|
|
557
569
|
}
|
|
558
570
|
if (settings.displayItemValue) {
|
|
559
|
-
parts.push(formatValue(item, settings
|
|
571
|
+
parts.push(formatValue(item, settings));
|
|
560
572
|
}
|
|
561
573
|
return parts.join(' ');
|
|
562
574
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const PERCENT = 100;
|
|
2
2
|
const STEP_COUNT = 10;
|
|
3
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
3
4
|
/**
|
|
4
5
|
* The most steps that an `axisIncrement` may divide a value axis into.
|
|
5
6
|
*/
|
|
@@ -102,6 +103,114 @@ export function getAxisRange(items, maxValue, axisIncrement) {
|
|
|
102
103
|
: getNiceStep(span);
|
|
103
104
|
return roundOutwards(minRange, finalMaxRange, increment);
|
|
104
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Whether `value` is a date in milliseconds that `Date` can hold. A date
|
|
108
|
+
* in nanoseconds is not, and neither is a date given as text.
|
|
109
|
+
* @param value - the value to check
|
|
110
|
+
*/
|
|
111
|
+
export function isDate(value) {
|
|
112
|
+
return (typeof value === 'number' && Number.isFinite(new Date(value).getTime()));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The range of an axis of dates, in milliseconds: from the start of the
|
|
116
|
+
* earliest day to the latest date, or to `maxValue` when that is set, as an
|
|
117
|
+
* axis of numbers ends at `maxValue`. An end during a day is moved to the
|
|
118
|
+
* end of that day. Unlike an axis of numbers, it does not reach down to
|
|
119
|
+
* zero, which would be 1970, and values that are not dates are left out.
|
|
120
|
+
* Its increment is a day, though `getDateTicks` decides what the axis
|
|
121
|
+
* marks.
|
|
122
|
+
* @param values - the dates on the axis
|
|
123
|
+
* @param maxValue - the requested upper bound, if any
|
|
124
|
+
*/
|
|
125
|
+
export function getDateRange(values, maxValue) {
|
|
126
|
+
const dates = values.filter((value) => isDate(value));
|
|
127
|
+
const start = new Date(dates.length > 0 ? Math.min(...dates) : Date.now());
|
|
128
|
+
start.setHours(0, 0, 0, 0);
|
|
129
|
+
const end = new Date(isDate(maxValue) ? maxValue : Math.max(start.getTime(), ...dates));
|
|
130
|
+
if (end.getTime() <= start.getTime()) {
|
|
131
|
+
end.setTime(addDays(start, 1).getTime());
|
|
132
|
+
}
|
|
133
|
+
else if (end.getTime() > startOfDay(end)) {
|
|
134
|
+
end.setTime(addDays(new Date(startOfDay(end)), 1).getTime());
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
minValue: start.getTime(),
|
|
138
|
+
maxValue: end.getTime(),
|
|
139
|
+
totalRange: end.getTime() - start.getTime(),
|
|
140
|
+
increment: DAY,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const DAYS_IN_MONTH = 30.44;
|
|
144
|
+
const DAYS_IN_YEAR = 365.25;
|
|
145
|
+
const DATE_STEP_COUNT = 6;
|
|
146
|
+
const dayStep = (count) => ({
|
|
147
|
+
unit: 'day',
|
|
148
|
+
days: count,
|
|
149
|
+
first: (date) => date,
|
|
150
|
+
next: (date) => addDays(date, count),
|
|
151
|
+
});
|
|
152
|
+
const weekStep = (count) => ({
|
|
153
|
+
unit: 'day',
|
|
154
|
+
days: 7 * count,
|
|
155
|
+
first: (date) => addDays(date, (8 - date.getDay()) % 7),
|
|
156
|
+
next: (date) => addDays(date, 7 * count),
|
|
157
|
+
});
|
|
158
|
+
const monthStep = (count) => ({
|
|
159
|
+
unit: 'month',
|
|
160
|
+
days: DAYS_IN_MONTH * count,
|
|
161
|
+
first: (date) => {
|
|
162
|
+
let month = date.getDate() === 1 ? date : getFirstOfMonth(date, 1);
|
|
163
|
+
while (month.getMonth() % count !== 0) {
|
|
164
|
+
month = getFirstOfMonth(month, 1);
|
|
165
|
+
}
|
|
166
|
+
return month;
|
|
167
|
+
},
|
|
168
|
+
next: (date) => getFirstOfMonth(date, count),
|
|
169
|
+
});
|
|
170
|
+
const yearStep = (count) => ({
|
|
171
|
+
unit: 'year',
|
|
172
|
+
days: DAYS_IN_YEAR * count,
|
|
173
|
+
first: (date) => {
|
|
174
|
+
let year = date.getFullYear();
|
|
175
|
+
if (date.getTime() > createDate(year, 0, 1).getTime()) {
|
|
176
|
+
year += 1;
|
|
177
|
+
}
|
|
178
|
+
return createDate(Math.ceil(year / count) * count, 0, 1);
|
|
179
|
+
},
|
|
180
|
+
next: (date) => createDate(date.getFullYear() + count, 0, 1),
|
|
181
|
+
});
|
|
182
|
+
const DATE_STEPS = [
|
|
183
|
+
dayStep(1),
|
|
184
|
+
dayStep(2),
|
|
185
|
+
weekStep(1),
|
|
186
|
+
weekStep(2),
|
|
187
|
+
monthStep(1),
|
|
188
|
+
monthStep(3),
|
|
189
|
+
monthStep(6),
|
|
190
|
+
yearStep(1),
|
|
191
|
+
yearStep(2),
|
|
192
|
+
yearStep(5),
|
|
193
|
+
yearStep(10),
|
|
194
|
+
];
|
|
195
|
+
/**
|
|
196
|
+
* The dates to mark on an axis of dates: days, Mondays, or the first days
|
|
197
|
+
* of months, quarters, half years or years, whichever divides the axis
|
|
198
|
+
* into at most about six steps. Dates take more room than numbers, so
|
|
199
|
+
* they get fewer steps.
|
|
200
|
+
* @param range - the range of the axis, from `getDateRange`
|
|
201
|
+
* @returns the dates to mark, and whether they are days, months or years
|
|
202
|
+
*/
|
|
203
|
+
export function getDateTicks(range) {
|
|
204
|
+
var _a;
|
|
205
|
+
const days = (range.maxValue - range.minValue) / DAY;
|
|
206
|
+
const decades = days / DAYS_IN_YEAR / 10;
|
|
207
|
+
const step = (_a = DATE_STEPS.find((candidate) => days / candidate.days <= DATE_STEP_COUNT)) !== null && _a !== void 0 ? _a : yearStep(10 * Math.ceil(decades / DATE_STEP_COUNT));
|
|
208
|
+
const ticks = [];
|
|
209
|
+
for (let date = step.first(new Date(range.minValue)); date.getTime() <= range.maxValue; date = step.next(date)) {
|
|
210
|
+
ticks.push(date.getTime());
|
|
211
|
+
}
|
|
212
|
+
return { values: ticks, unit: step.unit };
|
|
213
|
+
}
|
|
105
214
|
/**
|
|
106
215
|
* The range of a chart whose items are parts of one whole: `maxValue`
|
|
107
216
|
* when set, otherwise the combined item values or range extents. The value
|
|
@@ -144,25 +253,6 @@ export function getScatterRanges(items) {
|
|
|
144
253
|
export function getShare(item, whole) {
|
|
145
254
|
return (getValueSpan(item) / whole.totalRange) * PERCENT;
|
|
146
255
|
}
|
|
147
|
-
/**
|
|
148
|
-
* The item's value as text: its `formattedValue` when set, otherwise its
|
|
149
|
-
* value, range, or scatter point.
|
|
150
|
-
* @param item - the item
|
|
151
|
-
* @param type - the chart type
|
|
152
|
-
*/
|
|
153
|
-
export function formatValue(item, type) {
|
|
154
|
-
const { value, formattedValue } = item;
|
|
155
|
-
if (formattedValue) {
|
|
156
|
-
return formattedValue;
|
|
157
|
-
}
|
|
158
|
-
if (Array.isArray(value)) {
|
|
159
|
-
if (type === 'scatter') {
|
|
160
|
-
return `(${value[0]}, ${value[1]})`;
|
|
161
|
-
}
|
|
162
|
-
return `${value[0]} — ${value[1]}`;
|
|
163
|
-
}
|
|
164
|
-
return `${value}`;
|
|
165
|
-
}
|
|
166
256
|
function isUsableStep(step, span) {
|
|
167
257
|
return step > 0 && span / step <= MAX_AXIS_STEPS;
|
|
168
258
|
}
|
|
@@ -181,6 +271,29 @@ function getZeroAnchoredRange(values) {
|
|
|
181
271
|
const maxValue = Math.max(0, ...values);
|
|
182
272
|
return roundOutwards(minValue, maxValue, getNiceStep(maxValue - minValue));
|
|
183
273
|
}
|
|
274
|
+
function addDays(date, days) {
|
|
275
|
+
return createDate(date.getFullYear(), date.getMonth(), date.getDate() + days);
|
|
276
|
+
}
|
|
277
|
+
function getFirstOfMonth(date, monthsLater) {
|
|
278
|
+
return createDate(date.getFullYear(), date.getMonth() + monthsLater, 1);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* The start of a day in local time. Unlike `new Date(year, month, day)`, it
|
|
282
|
+
* keeps the years 0 to 99 rather than reading them as 1900 to 1999, see
|
|
283
|
+
* https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-makefullyear
|
|
284
|
+
* @param year - the full year
|
|
285
|
+
* @param month - the month, from 0 for January
|
|
286
|
+
* @param day - the day of the month
|
|
287
|
+
*/
|
|
288
|
+
function createDate(year, month, day) {
|
|
289
|
+
const date = new Date(0);
|
|
290
|
+
date.setFullYear(year, month, day);
|
|
291
|
+
date.setHours(0, 0, 0, 0);
|
|
292
|
+
return date;
|
|
293
|
+
}
|
|
294
|
+
function startOfDay(date) {
|
|
295
|
+
return new Date(date).setHours(0, 0, 0, 0);
|
|
296
|
+
}
|
|
184
297
|
function roundOutwards(minValue, maxValue, increment) {
|
|
185
298
|
const lowerEnd = Math.floor(minValue / increment) * increment;
|
|
186
299
|
const upperEnd = Math.ceil(maxValue / increment) * increment;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { h, Host, } from "@stencil/core";
|
|
2
2
|
import translate from "../../global/translations";
|
|
3
3
|
import { createRandomString } from "../../util/random-string";
|
|
4
|
-
import {
|
|
4
|
+
import { getSupportedLocale } from "../../util/language";
|
|
5
5
|
import { getPageSlots } from "./pagination.util";
|
|
6
6
|
const FIRST_PAGE = 1;
|
|
7
7
|
const DEFAULT_PAGE_SIZE = 100;
|
|
@@ -304,7 +304,7 @@ export class Pagination {
|
|
|
304
304
|
}
|
|
305
305
|
render() {
|
|
306
306
|
const { slots } = this.view;
|
|
307
|
-
return (h(Host, { key: '
|
|
307
|
+
return (h(Host, { key: '7c47cdc658859b4cd8199ef92ebd9bc48275a8ae' }, h("nav", { key: '3bba45547a7183a4b1df58294a56788f9b2e078f', "aria-label": this.translate('pagination.label'), "aria-busy": this.loading ? 'true' : null }, this.renderArrow('previous'), slots.map((slot, index) => this.renderSlot(slot, index)), this.renderArrow('next')), h("span", { key: '60fff33d8e135b1605d0a0f68b6c1ede1b571281', class: "live-region", "aria-live": "polite" }, this.liveMessage)));
|
|
308
308
|
}
|
|
309
309
|
/**
|
|
310
310
|
* Without a count we know where the user is but not how far the set goes,
|
|
@@ -620,9 +620,7 @@ export class Pagination {
|
|
|
620
620
|
}
|
|
621
621
|
/**
|
|
622
622
|
* What the jump form is given, so that the one warning about a language
|
|
623
|
-
* this browser cannot write numbers in describes both of us.
|
|
624
|
-
* accepts but does not support, `zz`, resolves to the viewer's own locale
|
|
625
|
-
* rather than throwing, so asking the formatter is the only way to know.
|
|
623
|
+
* this browser cannot write numbers in describes both of us.
|
|
626
624
|
*/
|
|
627
625
|
get languageForNumbers() {
|
|
628
626
|
var _a;
|
|
@@ -630,31 +628,21 @@ export class Pagination {
|
|
|
630
628
|
return this.settledLanguage;
|
|
631
629
|
}
|
|
632
630
|
/**
|
|
633
|
-
* `
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
* `src/util/language.ts`
|
|
637
|
-
*
|
|
638
|
-
* A tag it cannot parse throws; a well-formed tag it does not support, like
|
|
639
|
-
* `zz`, does not — it quietly resolves to whatever locale the machine runs
|
|
640
|
-
* in. Both end up formatting numbers in something other than what was
|
|
641
|
-
* asked for, so both are worth saying out loud.
|
|
631
|
+
* This is called from `render`, so a language that `Intl` cannot write
|
|
632
|
+
* numbers in must not throw, or the control would draw nothing at all —
|
|
633
|
+
* the single state a user cannot click their way out of.
|
|
634
|
+
* `getSupportedLocale` in `src/util/language.ts` decides which languages
|
|
635
|
+
* those are.
|
|
642
636
|
*
|
|
643
637
|
* @returns a formatter, in the given language where that is possible
|
|
644
638
|
*/
|
|
645
639
|
createNumberFormat() {
|
|
646
640
|
const { language } = this;
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
this.settledLanguage = language;
|
|
653
|
-
return format;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
catch (_a) {
|
|
657
|
-
// Fall through to the warning below.
|
|
641
|
+
const locale = getSupportedLocale(language);
|
|
642
|
+
if (locale) {
|
|
643
|
+
this.warnedAbout.delete('language');
|
|
644
|
+
this.settledLanguage = language;
|
|
645
|
+
return new Intl.NumberFormat(locale);
|
|
658
646
|
}
|
|
659
647
|
this.warnOnce('language', language, () => `\`language\` must be one this browser can write numbers in, but was ${JSON.stringify(language)}. Using ${FALLBACK_LANGUAGE}.`);
|
|
660
648
|
// Where the translations land for the same unusable value, rather than
|
|
@@ -22,3 +22,32 @@ export function getPrimarySubtag(language) {
|
|
|
22
22
|
}
|
|
23
23
|
return language.toLowerCase().split('-', 1)[0];
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The locale that `Intl` writes numbers and dates in for `language`, or
|
|
27
|
+
* `undefined` when it cannot write in that language. `Intl` throws on a
|
|
28
|
+
* tag it cannot read, such as `sv_SE`, and quietly uses the viewer's own
|
|
29
|
+
* locale for one it does not know, such as `zz`. A component that writes in
|
|
30
|
+
* its `language` while it renders checks it here, and falls back to English
|
|
31
|
+
* when this gives `undefined`.
|
|
32
|
+
*
|
|
33
|
+
* Examples:
|
|
34
|
+
* - "sv" => "sv"
|
|
35
|
+
* - "SV-se" => "sv-SE"
|
|
36
|
+
* - "sv_SE" => undefined
|
|
37
|
+
* - "zz" => undefined
|
|
38
|
+
* - undefined => undefined
|
|
39
|
+
*
|
|
40
|
+
* @param language - the language to write in
|
|
41
|
+
* @returns the locale, or `undefined`
|
|
42
|
+
*/
|
|
43
|
+
export function getSupportedLocale(language) {
|
|
44
|
+
if (typeof language !== 'string') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return Intl.NumberFormat.supportedLocalesOf(language)[0];
|
|
49
|
+
}
|
|
50
|
+
catch (_a) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|