@adecore/ui 0.31.11 → 0.32.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/dist/AccentSwatches.js +290 -13
- package/dist/Banner.js +90 -9
- package/dist/Button.js +114 -22
- package/dist/ButtonGroup.js +62 -8
- package/dist/Checkbox.js +73 -4
- package/dist/ChoiceCards.js +205 -11
- package/dist/CloseButton.js +87 -7
- package/dist/ColorSwatch.js +132 -15
- package/dist/ColorWheelPicker.js +305 -71
- package/dist/ColumnResizeHandle.js +43 -8
- package/dist/DisabledReason.js +27 -8
- package/dist/DockShell.js +144 -37
- package/dist/EmptyState.js +95 -6
- package/dist/ErrorBoundary.js +254 -31
- package/dist/Field.js +349 -31
- package/dist/FileIcon.js +67 -6
- package/dist/Icon.js +38 -6
- package/dist/IconButton.js +176 -20
- package/dist/IconPicker.js +344 -73
- package/dist/Input.js +233 -14
- package/dist/Kbd.js +150 -10
- package/dist/LazyBoundary.js +14 -5
- package/dist/ListRow.js +75 -15
- package/dist/Meter.js +108 -5
- package/dist/MiddleTruncate.js +126 -45
- package/dist/PanelEmpty.js +64 -5
- package/dist/PanelHeader.js +95 -13
- package/dist/Pill.js +114 -19
- package/dist/ProjectSwitcher.js +225 -12
- package/dist/PromptDialog.js +352 -37
- package/dist/SectionLabel.js +62 -8
- package/dist/SegmentBar.js +197 -36
- package/dist/Segmented.js +130 -23
- package/dist/Select.js +310 -19
- package/dist/SelectSearch.js +344 -29
- package/dist/Separator.js +35 -4
- package/dist/ShortcutHints.js +146 -76
- package/dist/Skeleton.js +31 -4
- package/dist/Slider.js +172 -10
- package/dist/SlidingColumn.js +194 -34
- package/dist/Spinner.js +100 -3
- package/dist/Stepper.js +232 -11
- package/dist/Surface.js +62 -8
- package/dist/Switch.js +60 -6
- package/dist/TextMenu.js +212 -17
- package/dist/Tile.js +149 -5
- package/dist/Toasts.js +391 -33
- package/dist/Tooltip.js +153 -16
- package/dist/UIProvider.js +62 -15
- package/dist/Waveform.js +362 -93
- package/dist/Wipe.js +259 -34
- package/dist/ZoomControls.js +356 -14
- package/dist/class-name.js +1 -1
- package/dist/clipboard.js +15 -17
- package/dist/context-menu/index.parts.js +1 -1
- package/dist/dialog/index.parts.js +1 -1
- package/dist/dialog/parts.js +378 -29
- package/dist/dialog-layer.js +51 -18
- package/dist/error-boundary.js +19 -19
- package/dist/error-message.js +1 -1
- package/dist/field-context.js +7 -5
- package/dist/file-icon.js +23 -21
- package/dist/file-tree/helpers.js +133 -119
- package/dist/file-tree/index.parts.js +3 -3
- package/dist/file-tree/parts.js +418 -127
- package/dist/file-tree/style.js +1 -1
- package/dist/file-tree/use-file-tree-shift.js +43 -30
- package/dist/file-tree/use-file-tree.js +60 -18
- package/dist/file-tree/use-slots.js +220 -192
- package/dist/floating.js +2 -2
- package/dist/format/datetime.js +88 -38
- package/dist/format/duration.js +59 -53
- package/dist/format/index.js +1 -1
- package/dist/format/locale.js +76 -56
- package/dist/format/number.js +77 -61
- package/dist/format/regions.js +4 -2
- package/dist/format/time-zone.js +1 -1
- package/dist/icon-button-size.js +12 -2
- package/dist/icon-picker.js +70 -61
- package/dist/index.js +57 -57
- package/dist/key-value-list/index.parts.js +1 -1
- package/dist/key-value-list/parts.js +256 -22
- package/dist/lazy-landings.js +10 -10
- package/dist/lazy.js +84 -71
- package/dist/locales/en.js +80 -80
- package/dist/locales/nl.js +80 -80
- package/dist/locales.js +10 -7
- package/dist/menu/index.parts.js +1 -1
- package/dist/menu/parts.js +882 -44
- package/dist/merge-refs.js +8 -9
- package/dist/middle-truncate.js +24 -25
- package/dist/modality.js +27 -19
- package/dist/placement.js +11 -20
- package/dist/platform.js +6 -6
- package/dist/popover/index.parts.js +1 -1
- package/dist/popover/parts.js +173 -11
- package/dist/popup-layer.js +38 -17
- package/dist/prefetch.js +78 -79
- package/dist/preview-card/index.parts.js +1 -1
- package/dist/project-switcher-row.js +153 -12
- package/dist/select-row.js +69 -5
- package/dist/select-shared.js +6 -6
- package/dist/selection.js +16 -16
- package/dist/seti-icons.js +413 -413
- package/dist/settings/ConfirmDialog.js +55 -8
- package/dist/settings/DetailHeader.js +72 -4
- package/dist/settings/MasterDetail.js +119 -7
- package/dist/settings/SettingsDialog.js +820 -80
- package/dist/settings/SettingsRow.js +205 -18
- package/dist/settings/SettingsSection.js +96 -6
- package/dist/settings/index.js +7 -7
- package/dist/settings/target.js +7 -2
- package/dist/shortcut-hints.js +73 -55
- package/dist/shortcut.js +140 -104
- package/dist/step-places.js +1 -1
- package/dist/tabs/index.parts.js +1 -1
- package/dist/tabs/overflow.js +18 -18
- package/dist/tabs/parts.js +780 -166
- package/dist/testing/dedupe.js +82 -75
- package/dist/testing/fake-source.js +12 -12
- package/dist/testing/index.js +1 -1
- package/dist/toast-store.js +65 -46
- package/dist/tree/index.parts.js +1 -1
- package/dist/tree/parts.js +721 -94
- package/dist/tree/shift.js +28 -22
- package/dist/tree/style.js +2 -2
- package/dist/tree/use-tree-shift.js +178 -117
- package/dist/useAsyncAction.js +23 -19
- package/dist/useColumnResize.js +36 -34
- package/dist/useContentSize.js +62 -18
- package/dist/useMeasuredWidth.js +14 -3
- package/dist/useNow.js +73 -30
- package/dist/wipe-split.js +14 -14
- package/dist/workspace/DropPreview.js +60 -7
- package/dist/workspace/SplitView.js +1025 -441
- package/dist/workspace/TabStrip.js +810 -191
- package/dist/workspace/Workspace.js +276 -26
- package/dist/workspace/context.js +8 -3
- package/dist/workspace/geometry.js +173 -150
- package/dist/workspace/model.js +313 -250
- package/dist/workspace/preview.js +61 -61
- package/dist/zoom.js +2 -2
- package/package.json +1 -1
- package/src/ErrorBoundary.tsx +1 -1
- package/src/Field.tsx +4 -3
- package/src/IconPicker.tsx +3 -3
- package/src/LazyBoundary.tsx +2 -0
- package/src/MiddleTruncate.tsx +2 -2
- package/src/SegmentBar.tsx +2 -2
- package/src/Toasts.tsx +3 -1
- package/src/Tooltip.tsx +7 -10
- package/src/Waveform.tsx +3 -3
- package/src/file-tree/parts.tsx +6 -9
- package/src/file-tree/use-file-tree-shift.tsx +6 -9
- package/src/lazy.tsx +4 -0
- package/src/locales/en.ts +4 -4
- package/src/locales/nl.ts +5 -5
- package/src/settings/SettingsDialog.tsx +1 -1
- package/src/tree/parts.tsx +5 -8
- package/src/useContentSize.ts +3 -3
- package/src/workspace/SplitView.tsx +2 -2
- package/src/workspace/TabStrip.tsx +2 -3
- package/src/workspace/Workspace.tsx +2 -5
package/dist/format/datetime.js
CHANGED
|
@@ -2,7 +2,7 @@ import { formatAgo } from './duration.js';
|
|
|
2
2
|
import { dateFormatter, wordFormatter } from './locale.js';
|
|
3
3
|
/* The parts that are a word rather than a number, and so come from the language and not the region. */
|
|
4
4
|
const WORD_PARTS = new Set(['month', 'weekday', 'dayPeriod', 'era']);
|
|
5
|
-
const toDate =
|
|
5
|
+
const toDate = at => typeof at === 'number' ? new Date(at) : at;
|
|
6
6
|
/*
|
|
7
7
|
* A date written the way the region writes one, with its words in the language the interface is in.
|
|
8
8
|
* Both formatters get the same options and the same instant, so every part the region asks for has
|
|
@@ -11,62 +11,112 @@ const toDate = (at) => (typeof at === 'number' ? new Date(at) : at);
|
|
|
11
11
|
* region does not say AM.
|
|
12
12
|
*/
|
|
13
13
|
export const formatDateTime = (at, options) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
const date = toDate(at);
|
|
15
|
+
const parts = dateFormatter(options).formatToParts(date);
|
|
16
|
+
if (!parts.some(part => WORD_PARTS.has(part.type))) {
|
|
17
|
+
return parts.map(part => part.value).join('');
|
|
18
|
+
}
|
|
19
|
+
const words = wordFormatter(options).formatToParts(date);
|
|
20
|
+
return parts.map(part => WORD_PARTS.has(part.type) ? words.find(word => word.type === part.type)?.value ?? part.value : part.value).join('');
|
|
21
|
+
};
|
|
22
|
+
const CLOCK = {
|
|
23
|
+
hour: '2-digit',
|
|
24
|
+
minute: '2-digit'
|
|
25
|
+
};
|
|
26
|
+
const WEEKDAY_CLOCK = {
|
|
27
|
+
weekday: 'short',
|
|
28
|
+
hour: '2-digit',
|
|
29
|
+
minute: '2-digit'
|
|
30
|
+
};
|
|
31
|
+
const HOUR = {
|
|
32
|
+
hour: 'numeric'
|
|
33
|
+
};
|
|
34
|
+
const DAY = {
|
|
35
|
+
month: 'short',
|
|
36
|
+
day: 'numeric'
|
|
37
|
+
};
|
|
38
|
+
const DAY_WITH_YEAR = {
|
|
39
|
+
month: 'short',
|
|
40
|
+
day: 'numeric',
|
|
41
|
+
year: 'numeric'
|
|
42
|
+
};
|
|
43
|
+
const WEEKDAY_DAY = {
|
|
44
|
+
weekday: 'short',
|
|
45
|
+
month: 'short',
|
|
46
|
+
day: 'numeric'
|
|
47
|
+
};
|
|
48
|
+
const DAY_CLOCK = {
|
|
49
|
+
month: 'short',
|
|
50
|
+
day: 'numeric',
|
|
51
|
+
hour: '2-digit',
|
|
52
|
+
minute: '2-digit'
|
|
53
|
+
};
|
|
54
|
+
const DAY_CLOCK_WITH_YEAR = {
|
|
55
|
+
month: 'short',
|
|
56
|
+
day: 'numeric',
|
|
57
|
+
year: 'numeric',
|
|
58
|
+
hour: '2-digit',
|
|
59
|
+
minute: '2-digit'
|
|
60
|
+
};
|
|
61
|
+
const NUMERIC_DATE = {
|
|
62
|
+
year: 'numeric',
|
|
63
|
+
month: 'numeric',
|
|
64
|
+
day: 'numeric'
|
|
21
65
|
};
|
|
22
|
-
const CLOCK = { hour: '2-digit', minute: '2-digit' };
|
|
23
|
-
const WEEKDAY_CLOCK = { weekday: 'short', hour: '2-digit', minute: '2-digit' };
|
|
24
|
-
const HOUR = { hour: 'numeric' };
|
|
25
|
-
const DAY = { month: 'short', day: 'numeric' };
|
|
26
|
-
const DAY_WITH_YEAR = { month: 'short', day: 'numeric', year: 'numeric' };
|
|
27
|
-
const WEEKDAY_DAY = { weekday: 'short', month: 'short', day: 'numeric' };
|
|
28
|
-
const DAY_CLOCK = { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' };
|
|
29
|
-
const DAY_CLOCK_WITH_YEAR = { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' };
|
|
30
|
-
const NUMERIC_DATE = { year: 'numeric', month: 'numeric', day: 'numeric' };
|
|
31
66
|
/*
|
|
32
67
|
* `08:05`, or `08:05 AM` in a region that counts to twelve. The clock functions below take the `timeZone`
|
|
33
68
|
* to write in, such as `UTC` for a moment whose parts are given and mean the same wall clock anywhere.
|
|
34
69
|
*/
|
|
35
|
-
export const formatClock = (at, timeZone) => formatDateTime(at, timeZone === undefined ? CLOCK : {
|
|
70
|
+
export const formatClock = (at, timeZone) => formatDateTime(at, timeZone === undefined ? CLOCK : {
|
|
71
|
+
...CLOCK,
|
|
72
|
+
timeZone
|
|
73
|
+
});
|
|
36
74
|
/* A time far enough off that its day is part of the answer: `Sat 08:05`. */
|
|
37
|
-
export const formatWeekdayClock =
|
|
75
|
+
export const formatWeekdayClock = at => formatDateTime(at, WEEKDAY_CLOCK);
|
|
38
76
|
/* An hour of the day on its own, for the foot of a chart: `08`, or `8 AM` where that is the clock. */
|
|
39
|
-
export const formatHour =
|
|
77
|
+
export const formatHour = at => formatDateTime(at, HOUR);
|
|
40
78
|
/* `19 Sep`, the short date a row falls back to once "days ago" stops meaning anything. */
|
|
41
|
-
export const formatDay = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY : {
|
|
79
|
+
export const formatDay = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY : {
|
|
80
|
+
...DAY,
|
|
81
|
+
timeZone
|
|
82
|
+
});
|
|
42
83
|
/* The same date with the year, for anything older than the one we are in. */
|
|
43
|
-
export const formatDayWithYear = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_WITH_YEAR : {
|
|
84
|
+
export const formatDayWithYear = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_WITH_YEAR : {
|
|
85
|
+
...DAY_WITH_YEAR,
|
|
86
|
+
timeZone
|
|
87
|
+
});
|
|
44
88
|
/* `Sat 19 Sep`. */
|
|
45
|
-
export const formatWeekdayDay =
|
|
89
|
+
export const formatWeekdayDay = at => formatDateTime(at, WEEKDAY_DAY);
|
|
46
90
|
/* `19 Sep, 08:05`: a moment that is not today, to the minute. */
|
|
47
|
-
export const formatDayClock = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_CLOCK : {
|
|
91
|
+
export const formatDayClock = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_CLOCK : {
|
|
92
|
+
...DAY_CLOCK,
|
|
93
|
+
timeZone
|
|
94
|
+
});
|
|
48
95
|
/* `19 Sep 2026, 08:05`: the same moment with the year, for one that is not in the year we are in. */
|
|
49
|
-
export const formatDayClockWithYear = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_CLOCK_WITH_YEAR : {
|
|
96
|
+
export const formatDayClockWithYear = (at, timeZone) => formatDateTime(at, timeZone === undefined ? DAY_CLOCK_WITH_YEAR : {
|
|
97
|
+
...DAY_CLOCK_WITH_YEAR,
|
|
98
|
+
timeZone
|
|
99
|
+
});
|
|
50
100
|
/* All numbers, as a form would print it: `19-9-2026` here, `9/19/2026` in the United States. */
|
|
51
|
-
export const formatNumericDate =
|
|
101
|
+
export const formatNumericDate = at => formatDateTime(at, NUMERIC_DATE);
|
|
52
102
|
/* Whether two instants fall on the same day of the same year, in the zone of whoever is reading. */
|
|
53
103
|
export const isSameDay = (a, b) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
const left = toDate(a);
|
|
105
|
+
const right = toDate(b);
|
|
106
|
+
return left.getFullYear() === right.getFullYear() && left.getMonth() === right.getMonth() && left.getDate() === right.getDate();
|
|
57
107
|
};
|
|
58
|
-
export const formatMoment = (at, now = Date.now()) =>
|
|
108
|
+
export const formatMoment = (at, now = Date.now()) => isSameDay(at, now) ? formatClock(at) : formatDayClock(at);
|
|
59
109
|
const WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
60
110
|
/*
|
|
61
111
|
* How old a row is, for its right edge: `3d ago` for the first week, then the day, with the year once
|
|
62
112
|
* it is not this one. An `at` ahead of `now`, from a clock that runs ahead, reads as now.
|
|
63
113
|
*/
|
|
64
114
|
export const formatAge = (at, now = Date.now()) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
115
|
+
const date = toDate(at);
|
|
116
|
+
const today = toDate(now);
|
|
117
|
+
const past = today.getTime() - date.getTime();
|
|
118
|
+
if (past < WEEK) {
|
|
119
|
+
return formatAgo(past);
|
|
120
|
+
}
|
|
121
|
+
return date.getFullYear() === today.getFullYear() ? formatDay(date) : formatDayWithYear(date);
|
|
122
|
+
};
|
package/dist/format/duration.js
CHANGED
|
@@ -9,68 +9,74 @@ const DAY = 24 * HOUR;
|
|
|
9
9
|
* abbreviations, the way the rest of the interface is written; only the number bends to the region,
|
|
10
10
|
* which is what `1,5 h` against `1.5 h` is about.
|
|
11
11
|
*/
|
|
12
|
-
export const formatDuration =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
export const formatDuration = ms => {
|
|
13
|
+
const seconds = Math.max(0, Math.round(ms / SECOND));
|
|
14
|
+
if (seconds < 60) {
|
|
15
|
+
return `${formatNumber(seconds)} s`;
|
|
16
|
+
}
|
|
17
|
+
const minutes = Math.round(seconds / 60);
|
|
18
|
+
if (minutes < 60) {
|
|
19
|
+
return `${formatNumber(minutes)} min`;
|
|
20
|
+
}
|
|
21
|
+
return `${formatDecimal(minutes / 60)} h`;
|
|
22
22
|
};
|
|
23
23
|
/* How long a window still has, tight enough for the end of a bar: `4d 3h`, `12h 8m`, `9m`. */
|
|
24
|
-
export const formatCountdown =
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
export const formatCountdown = ms => {
|
|
25
|
+
const left = Math.max(0, ms);
|
|
26
|
+
if (left >= DAY) {
|
|
27
|
+
return `${formatNumber(Math.floor(left / DAY))}d ${formatNumber(Math.round(left % DAY / HOUR))}h`;
|
|
28
|
+
}
|
|
29
|
+
if (left >= HOUR) {
|
|
30
|
+
return `${formatNumber(Math.floor(left / HOUR))}h ${formatNumber(Math.round(left % HOUR / MINUTE))}m`;
|
|
31
|
+
}
|
|
32
|
+
return `${formatNumber(Math.max(1, Math.round(left / MINUTE)))}m`;
|
|
33
33
|
};
|
|
34
34
|
/*
|
|
35
35
|
* How long something has been running, to two units and no space to spare: `12s`, `2m 5s`, `3h 20m`.
|
|
36
36
|
* A call that took a fraction of a second still took some, so anything above zero reads as at least
|
|
37
37
|
* `1s`; only a clock that runs ahead of the start comes out as `0s`.
|
|
38
38
|
*/
|
|
39
|
-
export const formatElapsedShort =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
export const formatElapsedShort = ms => {
|
|
40
|
+
const seconds = ms <= 0 ? 0 : Math.max(1, Math.round(ms / SECOND));
|
|
41
|
+
if (seconds < 60) {
|
|
42
|
+
return `${formatNumber(seconds)}s`;
|
|
43
|
+
}
|
|
44
|
+
if (seconds < 3600) {
|
|
45
|
+
const rest = seconds % 60;
|
|
46
|
+
const minutes = Math.floor(seconds / 60);
|
|
47
|
+
return rest === 0 ? `${formatNumber(minutes)}m` : `${formatNumber(minutes)}m ${formatNumber(rest)}s`;
|
|
48
|
+
}
|
|
49
|
+
const hours = Math.floor(seconds / 3600);
|
|
50
|
+
const minutes = Math.floor(seconds % 3600 / 60);
|
|
51
|
+
return minutes === 0 ? `${formatNumber(hours)}h` : `${formatNumber(hours)}h ${formatNumber(minutes)}m`;
|
|
52
52
|
};
|
|
53
53
|
/* A stopwatch that is watched while it runs: `00:14`, and `1:02:03` once it passes an hour. */
|
|
54
|
-
export const formatClockDuration =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
export const formatClockDuration = ms => {
|
|
55
|
+
const seconds = Math.max(0, Math.floor(ms / SECOND));
|
|
56
|
+
const pad = value => String(value).padStart(2, '0');
|
|
57
|
+
const clock = `${pad(Math.floor(seconds % 3600 / 60))}:${pad(seconds % 60)}`;
|
|
58
|
+
const hours = Math.floor(seconds / 3600);
|
|
59
|
+
return hours > 0 ? `${formatNumber(hours)}:${clock}` : clock;
|
|
60
60
|
};
|
|
61
|
-
const AGO = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
if (past < HOUR) {
|
|
70
|
-
return relativeFormatter(AGO).format(-Math.floor(past / MINUTE), 'minute');
|
|
71
|
-
}
|
|
72
|
-
if (past < DAY) {
|
|
73
|
-
return relativeFormatter(AGO).format(-Math.floor(past / HOUR), 'hour');
|
|
74
|
-
}
|
|
75
|
-
return relativeFormatter(AGO).format(-Math.floor(past / DAY), 'day');
|
|
61
|
+
const AGO = {
|
|
62
|
+
style: 'narrow',
|
|
63
|
+
numeric: 'always'
|
|
64
|
+
};
|
|
65
|
+
const NOW = {
|
|
66
|
+
style: 'narrow',
|
|
67
|
+
numeric: 'auto'
|
|
76
68
|
};
|
|
69
|
+
/* How long ago something was, short enough for the right edge of a row: `3m ago`, `3 min. geleden`. */
|
|
70
|
+
export const formatAgo = ms => {
|
|
71
|
+
const past = Math.max(0, ms);
|
|
72
|
+
if (past < MINUTE) {
|
|
73
|
+
return relativeFormatter(NOW).format(0, 'second');
|
|
74
|
+
}
|
|
75
|
+
if (past < HOUR) {
|
|
76
|
+
return relativeFormatter(AGO).format(-Math.floor(past / MINUTE), 'minute');
|
|
77
|
+
}
|
|
78
|
+
if (past < DAY) {
|
|
79
|
+
return relativeFormatter(AGO).format(-Math.floor(past / HOUR), 'hour');
|
|
80
|
+
}
|
|
81
|
+
return relativeFormatter(AGO).format(-Math.floor(past / DAY), 'day');
|
|
82
|
+
};
|
package/dist/format/index.js
CHANGED
|
@@ -4,4 +4,4 @@ export { formatAgo, formatClockDuration, formatCountdown, formatDuration, format
|
|
|
4
4
|
export { formatLocale, labelCollator, setFormatSource, systemLocale, useFormatLocale } from './locale.js';
|
|
5
5
|
export { formatBytes, formatDecimal, formatMoney, formatNumber, formatNumeral, formatPercent, formatRounded, formatTokens, formatUsdSignificant } from './number.js';
|
|
6
6
|
export { FORMAT_LANGUAGE, FORMAT_REGIONS, FORMAT_REGION_CHOICES, FORMAT_SYSTEM, formatRegionFrom, regionName } from './regions.js';
|
|
7
|
-
export { localTimeZone } from './time-zone.js';
|
|
7
|
+
export { localTimeZone } from './time-zone.js';
|
package/dist/format/locale.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { useSyncExternalStore } from 'react';
|
|
2
2
|
import { FORMAT_LANGUAGE, FORMAT_SYSTEM } from './regions.js';
|
|
3
|
-
let source = {
|
|
3
|
+
let source = {
|
|
4
|
+
language: () => 'en',
|
|
5
|
+
region: () => FORMAT_LANGUAGE,
|
|
6
|
+
subscribe: () => () => {}
|
|
7
|
+
};
|
|
4
8
|
/* Answers the source it replaced, so a test can put that one back. */
|
|
5
|
-
export const setFormatSource =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
export const setFormatSource = next => {
|
|
10
|
+
const previous = source;
|
|
11
|
+
source = next;
|
|
12
|
+
return previous;
|
|
9
13
|
};
|
|
10
14
|
/* Where a language comes from when nothing else says: the country most of its speakers are in. */
|
|
11
|
-
const LANGUAGE_REGIONS = {
|
|
15
|
+
const LANGUAGE_REGIONS = {
|
|
16
|
+
en: 'en-US',
|
|
17
|
+
nl: 'nl-NL'
|
|
18
|
+
};
|
|
12
19
|
/* The one locale to fall back on, so a format never depends on which machine ran the test. */
|
|
13
20
|
export const FALLBACK_LOCALE = 'en-US';
|
|
14
21
|
/* A month and a weekday are words, so they come from the language the interface is in, while the
|
|
@@ -17,16 +24,15 @@ export const FALLBACK_LOCALE = 'en-US';
|
|
|
17
24
|
export const wordLocale = () => source.language();
|
|
18
25
|
/* A tag `Intl` will take, or nothing. macOS hands out locales with overrides attached, and a
|
|
19
26
|
formatter built on one it does not know throws where a number was meant to go. */
|
|
20
|
-
const usable =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
27
|
+
const usable = tag => {
|
|
28
|
+
if (tag === undefined || tag === '') {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return Intl.DateTimeFormat.supportedLocalesOf([tag]).length > 0 ? tag : null;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
30
36
|
};
|
|
31
37
|
/*
|
|
32
38
|
* What the operating system was set to read numbers and dates in. In a desktop shell Chromium's own
|
|
@@ -35,14 +41,14 @@ const usable = (tag) => {
|
|
|
35
41
|
* (`systemLocale`). A browser has no such thing, so there the language is the best there is.
|
|
36
42
|
*/
|
|
37
43
|
export const systemLocale = () => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const fromShell = usable(source.systemLocale?.());
|
|
45
|
+
if (fromShell !== null) {
|
|
46
|
+
return fromShell;
|
|
47
|
+
}
|
|
48
|
+
if (typeof navigator === 'undefined') {
|
|
49
|
+
return FALLBACK_LOCALE;
|
|
50
|
+
}
|
|
51
|
+
return usable(navigator.languages?.[0] ?? navigator.language) ?? FALLBACK_LOCALE;
|
|
46
52
|
};
|
|
47
53
|
/*
|
|
48
54
|
* The region a language brings with it. The system's own when it already speaks that language, so
|
|
@@ -50,48 +56,53 @@ export const systemLocale = () => {
|
|
|
50
56
|
* in when it does not.
|
|
51
57
|
*/
|
|
52
58
|
const regionOfLanguage = () => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
const language = source.language();
|
|
60
|
+
const system = systemLocale();
|
|
61
|
+
return system.toLowerCase().startsWith(language) ? system : LANGUAGE_REGIONS[language] ?? language;
|
|
56
62
|
};
|
|
57
|
-
const resolve =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
63
|
+
const resolve = region => {
|
|
64
|
+
if (region === FORMAT_LANGUAGE) {
|
|
65
|
+
return regionOfLanguage();
|
|
66
|
+
}
|
|
67
|
+
return region === FORMAT_SYSTEM ? systemLocale() : region;
|
|
62
68
|
};
|
|
63
69
|
/* The locale every formatter in this folder is built on. Read outside React as well, so it is a
|
|
64
70
|
plain function over the source rather than a hook. */
|
|
65
71
|
export const formatLocale = () => resolve(source.region());
|
|
66
|
-
const subscribe =
|
|
72
|
+
const subscribe = onChange => source.subscribe(onChange);
|
|
67
73
|
/* What a component calls to draw again once the region changes. The value is the locale, which a
|
|
68
74
|
caller may use or ignore; subscribing is the point. */
|
|
69
|
-
export const useFormatLocale = () =>
|
|
75
|
+
export const useFormatLocale = () => {
|
|
76
|
+
return useSyncExternalStore(subscribe, formatLocale, formatLocale);
|
|
77
|
+
};
|
|
70
78
|
/*
|
|
71
79
|
* A formatter per options object, keyed on the object itself, so a module-level spec builds its
|
|
72
80
|
* formatter once and a list of a thousand rows constructs nothing. A region change throws the entry
|
|
73
81
|
* away, since the locale is part of what was cached.
|
|
74
82
|
*/
|
|
75
83
|
const cacheFor = (store, options, locale, build) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
84
|
+
const hit = store.get(options);
|
|
85
|
+
if (hit !== undefined && hit.locale === locale) {
|
|
86
|
+
return hit.formatter;
|
|
87
|
+
}
|
|
88
|
+
const formatter = build(locale, options);
|
|
89
|
+
store.set(options, {
|
|
90
|
+
locale,
|
|
91
|
+
formatter
|
|
92
|
+
});
|
|
93
|
+
return formatter;
|
|
83
94
|
};
|
|
84
95
|
const numbers = new WeakMap();
|
|
85
96
|
const dates = new WeakMap();
|
|
86
97
|
const words = new WeakMap();
|
|
87
98
|
const relatives = new WeakMap();
|
|
88
|
-
export const numberFormatter =
|
|
89
|
-
export const dateFormatter =
|
|
99
|
+
export const numberFormatter = options => cacheFor(numbers, options, formatLocale(), (locale, spec) => new Intl.NumberFormat(locale, spec));
|
|
100
|
+
export const dateFormatter = options => cacheFor(dates, options, formatLocale(), (locale, spec) => new Intl.DateTimeFormat(locale, spec));
|
|
90
101
|
/* The same date, written in the language the words come from. Never shown whole: only its month,
|
|
91
102
|
weekday, era and day period are lifted out of it. */
|
|
92
|
-
export const wordFormatter =
|
|
103
|
+
export const wordFormatter = options => cacheFor(words, options, wordLocale(), (locale, spec) => new Intl.DateTimeFormat(locale, spec));
|
|
93
104
|
/* `3 min. geleden` is words as much as a number, so the language writes it, the way it writes a month. */
|
|
94
|
-
export const relativeFormatter =
|
|
105
|
+
export const relativeFormatter = options => cacheFor(relatives, options, wordLocale(), (locale, spec) => new Intl.RelativeTimeFormat(locale, spec));
|
|
95
106
|
const collators = new Map();
|
|
96
107
|
/*
|
|
97
108
|
* How two labels a person reads are put in order. Labels are words, so the language writes them and
|
|
@@ -99,14 +110,17 @@ const collators = new Map();
|
|
|
99
110
|
* Keyed on the locale rather than on an options object, since there are no options to key on.
|
|
100
111
|
*/
|
|
101
112
|
export const labelCollator = () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
const locale = wordLocale();
|
|
114
|
+
const held = collators.get(locale);
|
|
115
|
+
if (held !== undefined) {
|
|
116
|
+
return held;
|
|
117
|
+
}
|
|
118
|
+
const collator = new Intl.Collator(locale, {
|
|
119
|
+
sensitivity: 'base',
|
|
120
|
+
numeric: true
|
|
121
|
+
});
|
|
122
|
+
collators.set(locale, collator);
|
|
123
|
+
return collator;
|
|
110
124
|
};
|
|
111
125
|
let nameCollators = null;
|
|
112
126
|
/*
|
|
@@ -114,6 +128,12 @@ let nameCollators = null;
|
|
|
114
128
|
* case only to break a tie. It is what `localeCompare` did, which built a collator on every call.
|
|
115
129
|
*/
|
|
116
130
|
export const nameOrder = (left, right) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
nameCollators ??= {
|
|
132
|
+
loose: new Intl.Collator(undefined, {
|
|
133
|
+
numeric: true,
|
|
134
|
+
sensitivity: 'base'
|
|
135
|
+
}),
|
|
136
|
+
exact: new Intl.Collator()
|
|
137
|
+
};
|
|
138
|
+
return nameCollators.loose.compare(left, right) || nameCollators.exact.compare(left, right);
|
|
139
|
+
};
|