@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/language-s0bKUCvO.js +58 -0
  3. package/dist/cjs/lime-elements.cjs.js +1 -1
  4. package/dist/cjs/limel-action-bar_3.cjs.entry.js +2 -2
  5. package/dist/cjs/limel-ai-avatar.cjs.entry.js +2 -2
  6. package/dist/cjs/limel-badge_7.cjs.entry.js +2 -2
  7. package/dist/cjs/limel-callout.cjs.entry.js +2 -2
  8. package/dist/cjs/limel-chart.cjs.entry.js +524 -76
  9. package/dist/cjs/limel-chip-set.cjs.entry.js +2 -2
  10. package/dist/cjs/limel-chip_2.cjs.entry.js +2 -2
  11. package/dist/cjs/limel-code-diff.cjs.entry.js +2 -2
  12. package/dist/cjs/limel-code-editor.cjs.entry.js +2 -2
  13. package/dist/cjs/limel-collapsible-section.cjs.entry.js +2 -2
  14. package/dist/cjs/limel-date-picker.cjs.entry.js +1 -1
  15. package/dist/cjs/limel-dock.cjs.entry.js +2 -2
  16. package/dist/cjs/limel-drag-handle_2.cjs.entry.js +2 -2
  17. package/dist/cjs/limel-email-viewer.cjs.entry.js +2 -2
  18. package/dist/cjs/limel-file-viewer.cjs.entry.js +2 -2
  19. package/dist/cjs/limel-file.cjs.entry.js +2 -2
  20. package/dist/cjs/limel-flatpickr-adapter.cjs.entry.js +2 -2
  21. package/dist/cjs/limel-list-item.cjs.entry.js +2 -2
  22. package/dist/cjs/limel-pagination-jump.cjs.entry.js +2 -2
  23. package/dist/cjs/limel-pagination.cjs.entry.js +14 -26
  24. package/dist/cjs/limel-picker.cjs.entry.js +2 -2
  25. package/dist/cjs/limel-profile-picture.cjs.entry.js +2 -2
  26. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +2 -2
  27. package/dist/cjs/limel-resize-handle.cjs.entry.js +2 -2
  28. package/dist/cjs/limel-side-panel.cjs.entry.js +2 -2
  29. package/dist/cjs/limel-slider.cjs.entry.js +2 -2
  30. package/dist/cjs/limel-snackbar.cjs.entry.js +2 -2
  31. package/dist/cjs/limel-table.cjs.entry.js +2 -2
  32. package/dist/cjs/limel-text-editor-link-menu.cjs.entry.js +2 -2
  33. package/dist/cjs/loader.cjs.js +1 -1
  34. package/dist/cjs/{translations-CqJEWcfw.js → translations-JLP5icQ9.js} +1 -1
  35. package/dist/collection/components/chart/chart.js +116 -45
  36. package/dist/collection/components/chart/format.js +302 -0
  37. package/dist/collection/components/chart/options.js +26 -14
  38. package/dist/collection/components/chart/scale.js +132 -19
  39. package/dist/collection/components/pagination/pagination.js +13 -25
  40. package/dist/collection/util/language.js +29 -0
  41. package/dist/esm/language-VamkUr90.js +55 -0
  42. package/dist/esm/lime-elements.js +1 -1
  43. package/dist/esm/limel-action-bar_3.entry.js +2 -2
  44. package/dist/esm/limel-ai-avatar.entry.js +2 -2
  45. package/dist/esm/limel-badge_7.entry.js +2 -2
  46. package/dist/esm/limel-callout.entry.js +2 -2
  47. package/dist/esm/limel-chart.entry.js +524 -76
  48. package/dist/esm/limel-chip-set.entry.js +2 -2
  49. package/dist/esm/limel-chip_2.entry.js +2 -2
  50. package/dist/esm/limel-code-diff.entry.js +2 -2
  51. package/dist/esm/limel-code-editor.entry.js +2 -2
  52. package/dist/esm/limel-collapsible-section.entry.js +2 -2
  53. package/dist/esm/limel-date-picker.entry.js +1 -1
  54. package/dist/esm/limel-dock.entry.js +2 -2
  55. package/dist/esm/limel-drag-handle_2.entry.js +2 -2
  56. package/dist/esm/limel-email-viewer.entry.js +2 -2
  57. package/dist/esm/limel-file-viewer.entry.js +2 -2
  58. package/dist/esm/limel-file.entry.js +2 -2
  59. package/dist/esm/limel-flatpickr-adapter.entry.js +2 -2
  60. package/dist/esm/limel-list-item.entry.js +2 -2
  61. package/dist/esm/limel-pagination-jump.entry.js +2 -2
  62. package/dist/esm/limel-pagination.entry.js +14 -26
  63. package/dist/esm/limel-picker.entry.js +2 -2
  64. package/dist/esm/limel-profile-picture.entry.js +2 -2
  65. package/dist/esm/limel-prosemirror-adapter.entry.js +2 -2
  66. package/dist/esm/limel-resize-handle.entry.js +2 -2
  67. package/dist/esm/limel-side-panel.entry.js +2 -2
  68. package/dist/esm/limel-slider.entry.js +2 -2
  69. package/dist/esm/limel-snackbar.entry.js +2 -2
  70. package/dist/esm/limel-table.entry.js +2 -2
  71. package/dist/esm/limel-text-editor-link-menu.entry.js +2 -2
  72. package/dist/esm/loader.js +1 -1
  73. package/dist/esm/{translations-kct5Kybu.js → translations-D9ZGrMhe.js} +1 -1
  74. package/dist/lime-elements/lime-elements.esm.js +1 -1
  75. package/dist/lime-elements/{p-6e9eeb0a.entry.js → p-0a7cbdc3.entry.js} +1 -1
  76. package/dist/lime-elements/{p-d35d2859.entry.js → p-0f96e94d.entry.js} +1 -1
  77. package/dist/lime-elements/{p-a7be03e1.entry.js → p-1a0c5e45.entry.js} +1 -1
  78. package/dist/lime-elements/{p-5e408c27.entry.js → p-401d5c92.entry.js} +1 -1
  79. package/dist/lime-elements/{p-6d2d25b9.entry.js → p-430db3b9.entry.js} +1 -1
  80. package/dist/lime-elements/{p-a6c6a31b.entry.js → p-43b62587.entry.js} +1 -1
  81. package/dist/lime-elements/{p-958396b5.entry.js → p-48dccb83.entry.js} +1 -1
  82. package/dist/lime-elements/{p-814b4934.entry.js → p-5c918155.entry.js} +1 -1
  83. package/dist/lime-elements/{p-22e9e327.entry.js → p-66101ed6.entry.js} +1 -1
  84. package/dist/lime-elements/{p-d8559847.entry.js → p-7ab72b52.entry.js} +1 -1
  85. package/dist/lime-elements/{p-7af83877.entry.js → p-8d9a2a22.entry.js} +1 -1
  86. package/dist/lime-elements/{p-55fa19ae.entry.js → p-8de4f0f3.entry.js} +1 -1
  87. package/dist/lime-elements/p-8e80a884.entry.js +56 -0
  88. package/dist/lime-elements/{p-bffb5ed2.entry.js → p-99984c66.entry.js} +1 -1
  89. package/dist/lime-elements/{p-56e2fa48.entry.js → p-9d46f720.entry.js} +1 -1
  90. package/dist/lime-elements/{p-68bbb8dd.entry.js → p-9e2b426c.entry.js} +1 -1
  91. package/dist/lime-elements/{p-Dzp8dXTo.js → p-C9dBa_Im.js} +1 -1
  92. package/dist/lime-elements/p-VamkUr90.js +1 -0
  93. package/dist/lime-elements/{p-3ac39bd5.entry.js → p-b1c7033b.entry.js} +2 -2
  94. package/dist/lime-elements/{p-47fe76a8.entry.js → p-b5451350.entry.js} +1 -1
  95. package/dist/lime-elements/{p-9edd79a8.entry.js → p-c75f3b0d.entry.js} +1 -1
  96. package/dist/lime-elements/{p-4c080cd9.entry.js → p-ca4247c4.entry.js} +1 -1
  97. package/dist/lime-elements/{p-1a7d1d2b.entry.js → p-d53421af.entry.js} +1 -1
  98. package/dist/lime-elements/{p-6d9b89db.entry.js → p-d8901e39.entry.js} +1 -1
  99. package/dist/lime-elements/{p-933ffaa8.entry.js → p-d89553b3.entry.js} +1 -1
  100. package/dist/lime-elements/{p-496b2437.entry.js → p-d93b8ce0.entry.js} +1 -1
  101. package/dist/lime-elements/{p-23067836.entry.js → p-e97340c4.entry.js} +1 -1
  102. package/dist/lime-elements/{p-7b87e547.entry.js → p-ec0c113d.entry.js} +1 -1
  103. package/dist/lime-elements/{p-9f0575fd.entry.js → p-f197000b.entry.js} +4 -4
  104. package/dist/lime-elements/{p-07f3565b.entry.js → p-f6bc6658.entry.js} +1 -1
  105. package/dist/lime-elements/{p-7fcd48c6.entry.js → p-fa1aa440.entry.js} +1 -1
  106. package/dist/types/components/chart/chart.d.ts +32 -7
  107. package/dist/types/components/chart/chart.types.d.ts +15 -1
  108. package/dist/types/components/chart/format.d.ts +54 -0
  109. package/dist/types/components/chart/options.d.ts +3 -4
  110. package/dist/types/components/chart/scale.d.ts +39 -7
  111. package/dist/types/components/pagination/pagination.d.ts +6 -12
  112. package/dist/types/components.d.ts +36 -8
  113. package/dist/types/util/language.d.ts +19 -0
  114. package/package.json +1 -1
  115. package/dist/cjs/language-v-MMS6ym.js +0 -28
  116. package/dist/esm/language-ChagAaNf.js +0 -26
  117. package/dist/lime-elements/p-7d6a12fe.entry.js +0 -56
  118. 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 { formatValue, getAxisRange, getMaximumValue, getMinimumValue, getScatterRanges, getXValue, getYValue, getShare, getValueSpan, getWholeRange, isPartOfWhole, isRangeItem, } from "./scale";
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).toFixed(PERCENT_DECIMALS);
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.type)]
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 = getAxisRange(items, settings.maxValue, settings.axisIncrement);
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 = getScatterRanges(items);
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
- return Object.assign({ type: 'value', min: range.minValue, max: range.maxValue, interval: range.increment, axisLabel: { show: true, color: theme.textColor }, axisLine: { lineStyle: { color: theme.axisLineColor } }, splitLine: { lineStyle: { color: theme.axisLineColor } } }, name);
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.type) });
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.type));
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 { getPrimarySubtag } from "../../util/language";
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: '924becb588f7d773572b61865b9dd79b14171153' }, h("nav", { key: 'f517b62cf0107c912d576f08e9d99594f868be44', "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: '21758520849acfe32f43e86570fe60a040cf2748', class: "live-region", "aria-live": "polite" }, this.liveMessage)));
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. A tag it
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
- * `Intl.NumberFormat` throws on a language it cannot parse, and this is
634
- * called from `render`, so an unusable one would leave the control drawing
635
- * nothing at all — the single state a user cannot click their way out of.
636
- * `src/util/language.ts` states the rule this keeps.
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
- try {
648
- const format = new Intl.NumberFormat(language);
649
- const resolved = format.resolvedOptions().locale;
650
- if (getPrimarySubtag(resolved) === getPrimarySubtag(language)) {
651
- this.warnedAbout.delete('language');
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
+ }