@adecore/ui 0.31.12 → 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.
Files changed (163) hide show
  1. package/dist/AccentSwatches.js +290 -13
  2. package/dist/Banner.js +90 -9
  3. package/dist/Button.js +114 -22
  4. package/dist/ButtonGroup.js +62 -8
  5. package/dist/Checkbox.js +73 -4
  6. package/dist/ChoiceCards.js +205 -11
  7. package/dist/CloseButton.js +87 -7
  8. package/dist/ColorSwatch.js +132 -15
  9. package/dist/ColorWheelPicker.js +305 -71
  10. package/dist/ColumnResizeHandle.js +43 -8
  11. package/dist/DisabledReason.js +27 -8
  12. package/dist/DockShell.js +144 -37
  13. package/dist/EmptyState.js +95 -6
  14. package/dist/ErrorBoundary.js +254 -31
  15. package/dist/Field.js +349 -31
  16. package/dist/FileIcon.js +67 -6
  17. package/dist/Icon.js +38 -6
  18. package/dist/IconButton.js +176 -20
  19. package/dist/IconPicker.js +344 -73
  20. package/dist/Input.js +233 -14
  21. package/dist/Kbd.js +150 -10
  22. package/dist/LazyBoundary.js +14 -5
  23. package/dist/ListRow.js +75 -15
  24. package/dist/Meter.js +108 -5
  25. package/dist/MiddleTruncate.js +126 -45
  26. package/dist/PanelEmpty.js +64 -5
  27. package/dist/PanelHeader.js +95 -13
  28. package/dist/Pill.js +114 -19
  29. package/dist/ProjectSwitcher.js +225 -12
  30. package/dist/PromptDialog.js +352 -37
  31. package/dist/SectionLabel.js +62 -8
  32. package/dist/SegmentBar.js +197 -36
  33. package/dist/Segmented.js +130 -23
  34. package/dist/Select.js +310 -19
  35. package/dist/SelectSearch.js +344 -29
  36. package/dist/Separator.js +35 -4
  37. package/dist/ShortcutHints.js +146 -76
  38. package/dist/Skeleton.js +31 -4
  39. package/dist/Slider.js +172 -10
  40. package/dist/SlidingColumn.js +194 -34
  41. package/dist/Spinner.js +100 -3
  42. package/dist/Stepper.js +232 -11
  43. package/dist/Surface.js +62 -8
  44. package/dist/Switch.js +60 -6
  45. package/dist/TextMenu.js +212 -17
  46. package/dist/Tile.js +149 -5
  47. package/dist/Toasts.js +391 -33
  48. package/dist/Tooltip.js +153 -16
  49. package/dist/UIProvider.js +62 -15
  50. package/dist/Waveform.js +362 -93
  51. package/dist/Wipe.js +259 -34
  52. package/dist/ZoomControls.js +356 -14
  53. package/dist/class-name.js +1 -1
  54. package/dist/clipboard.js +15 -17
  55. package/dist/context-menu/index.parts.js +1 -1
  56. package/dist/dialog/index.parts.js +1 -1
  57. package/dist/dialog/parts.js +378 -29
  58. package/dist/dialog-layer.js +51 -18
  59. package/dist/error-boundary.js +19 -19
  60. package/dist/error-message.js +1 -1
  61. package/dist/field-context.js +7 -5
  62. package/dist/file-icon.js +23 -21
  63. package/dist/file-tree/helpers.js +133 -119
  64. package/dist/file-tree/index.parts.js +3 -3
  65. package/dist/file-tree/parts.js +418 -127
  66. package/dist/file-tree/style.js +1 -1
  67. package/dist/file-tree/use-file-tree-shift.js +43 -30
  68. package/dist/file-tree/use-file-tree.js +60 -18
  69. package/dist/file-tree/use-slots.js +220 -192
  70. package/dist/floating.js +2 -2
  71. package/dist/format/datetime.js +88 -38
  72. package/dist/format/duration.js +59 -53
  73. package/dist/format/index.js +1 -1
  74. package/dist/format/locale.js +76 -56
  75. package/dist/format/number.js +77 -61
  76. package/dist/format/regions.js +4 -2
  77. package/dist/format/time-zone.js +1 -1
  78. package/dist/icon-button-size.js +12 -2
  79. package/dist/icon-picker.js +70 -61
  80. package/dist/index.js +57 -57
  81. package/dist/key-value-list/index.parts.js +1 -1
  82. package/dist/key-value-list/parts.js +256 -22
  83. package/dist/lazy-landings.js +10 -10
  84. package/dist/lazy.js +84 -71
  85. package/dist/locales/en.js +80 -80
  86. package/dist/locales/nl.js +80 -80
  87. package/dist/locales.js +10 -7
  88. package/dist/menu/index.parts.js +1 -1
  89. package/dist/menu/parts.js +882 -44
  90. package/dist/merge-refs.js +8 -9
  91. package/dist/middle-truncate.js +24 -25
  92. package/dist/modality.js +27 -19
  93. package/dist/placement.js +11 -20
  94. package/dist/platform.js +6 -6
  95. package/dist/popover/index.parts.js +1 -1
  96. package/dist/popover/parts.js +173 -11
  97. package/dist/popup-layer.js +38 -17
  98. package/dist/prefetch.js +78 -79
  99. package/dist/preview-card/index.parts.js +1 -1
  100. package/dist/project-switcher-row.js +153 -12
  101. package/dist/select-row.js +69 -5
  102. package/dist/select-shared.js +6 -6
  103. package/dist/selection.js +16 -16
  104. package/dist/seti-icons.js +413 -413
  105. package/dist/settings/ConfirmDialog.js +55 -8
  106. package/dist/settings/DetailHeader.js +72 -4
  107. package/dist/settings/MasterDetail.js +119 -7
  108. package/dist/settings/SettingsDialog.js +820 -80
  109. package/dist/settings/SettingsRow.js +205 -18
  110. package/dist/settings/SettingsSection.js +96 -6
  111. package/dist/settings/index.js +7 -7
  112. package/dist/settings/target.js +7 -2
  113. package/dist/shortcut-hints.js +73 -55
  114. package/dist/shortcut.js +140 -104
  115. package/dist/step-places.js +1 -1
  116. package/dist/tabs/index.parts.js +1 -1
  117. package/dist/tabs/overflow.js +18 -18
  118. package/dist/tabs/parts.js +780 -166
  119. package/dist/testing/dedupe.js +82 -75
  120. package/dist/testing/fake-source.js +12 -12
  121. package/dist/testing/index.js +1 -1
  122. package/dist/toast-store.js +65 -46
  123. package/dist/tree/index.parts.js +1 -1
  124. package/dist/tree/parts.js +721 -94
  125. package/dist/tree/shift.js +28 -22
  126. package/dist/tree/style.js +2 -2
  127. package/dist/tree/use-tree-shift.js +178 -117
  128. package/dist/useAsyncAction.js +23 -19
  129. package/dist/useColumnResize.js +36 -34
  130. package/dist/useContentSize.js +62 -18
  131. package/dist/useMeasuredWidth.js +14 -3
  132. package/dist/useNow.js +73 -30
  133. package/dist/wipe-split.js +14 -14
  134. package/dist/workspace/DropPreview.js +60 -7
  135. package/dist/workspace/SplitView.js +1025 -441
  136. package/dist/workspace/TabStrip.js +810 -191
  137. package/dist/workspace/Workspace.js +276 -26
  138. package/dist/workspace/context.js +8 -3
  139. package/dist/workspace/geometry.js +173 -150
  140. package/dist/workspace/model.js +313 -250
  141. package/dist/workspace/preview.js +61 -61
  142. package/dist/zoom.js +2 -2
  143. package/package.json +1 -1
  144. package/src/ErrorBoundary.tsx +1 -1
  145. package/src/Field.tsx +4 -3
  146. package/src/IconPicker.tsx +3 -3
  147. package/src/LazyBoundary.tsx +2 -0
  148. package/src/MiddleTruncate.tsx +2 -2
  149. package/src/SegmentBar.tsx +2 -2
  150. package/src/Toasts.tsx +3 -1
  151. package/src/Tooltip.tsx +7 -10
  152. package/src/Waveform.tsx +3 -3
  153. package/src/file-tree/parts.tsx +6 -9
  154. package/src/file-tree/use-file-tree-shift.tsx +6 -9
  155. package/src/lazy.tsx +4 -0
  156. package/src/locales/en.ts +4 -4
  157. package/src/locales/nl.ts +5 -5
  158. package/src/settings/SettingsDialog.tsx +1 -1
  159. package/src/tree/parts.tsx +5 -8
  160. package/src/useContentSize.ts +3 -3
  161. package/src/workspace/SplitView.tsx +2 -2
  162. package/src/workspace/TabStrip.tsx +2 -3
  163. package/src/workspace/Workspace.tsx +2 -5
@@ -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 = (at) => (typeof at === 'number' ? new Date(at) : at);
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
- 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('');
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 : { ...CLOCK, timeZone });
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 = (at) => formatDateTime(at, WEEKDAY_CLOCK);
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 = (at) => formatDateTime(at, HOUR);
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 : { ...DAY, timeZone });
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 : { ...DAY_WITH_YEAR, timeZone });
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 = (at) => formatDateTime(at, WEEKDAY_DAY);
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 : { ...DAY_CLOCK, timeZone });
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 : { ...DAY_CLOCK_WITH_YEAR, timeZone });
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 = (at) => formatDateTime(at, NUMERIC_DATE);
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
- const left = toDate(a);
55
- const right = toDate(b);
56
- return left.getFullYear() === right.getFullYear() && left.getMonth() === right.getMonth() && left.getDate() === right.getDate();
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()) => (isSameDay(at, now) ? formatClock(at) : formatDayClock(at));
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
- const date = toDate(at);
66
- const today = toDate(now);
67
- const past = today.getTime() - date.getTime();
68
- if (past < WEEK) {
69
- return formatAgo(past);
70
- }
71
- return date.getFullYear() === today.getFullYear() ? formatDay(date) : formatDayWithYear(date);
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
+ };
@@ -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 = (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`;
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 = (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`;
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 = (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`;
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 = (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;
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 = { style: 'narrow', numeric: 'always' };
62
- const NOW = { style: 'narrow', numeric: 'auto' };
63
- /* How long ago something was, short enough for the right edge of a row: `3m ago`, `3 min. geleden`. */
64
- export const formatAgo = (ms) => {
65
- const past = Math.max(0, ms);
66
- if (past < MINUTE) {
67
- return relativeFormatter(NOW).format(0, 'second');
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
+ };
@@ -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';
@@ -1,14 +1,21 @@
1
1
  import { useSyncExternalStore } from 'react';
2
2
  import { FORMAT_LANGUAGE, FORMAT_SYSTEM } from './regions.js';
3
- let source = { language: () => 'en', region: () => FORMAT_LANGUAGE, subscribe: () => () => { } };
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 = (next) => {
6
- const previous = source;
7
- source = next;
8
- return previous;
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 = { en: 'en-US', nl: 'nl-NL' };
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 = (tag) => {
21
- if (tag === undefined || tag === '') {
22
- return null;
23
- }
24
- try {
25
- return Intl.DateTimeFormat.supportedLocalesOf([tag]).length > 0 ? tag : null;
26
- }
27
- catch {
28
- return null;
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
- const fromShell = usable(source.systemLocale?.());
39
- if (fromShell !== null) {
40
- return fromShell;
41
- }
42
- if (typeof navigator === 'undefined') {
43
- return FALLBACK_LOCALE;
44
- }
45
- return usable(navigator.languages?.[0] ?? navigator.language) ?? FALLBACK_LOCALE;
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
- const language = source.language();
54
- const system = systemLocale();
55
- return system.toLowerCase().startsWith(language) ? system : (LANGUAGE_REGIONS[language] ?? language);
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 = (region) => {
58
- if (region === FORMAT_LANGUAGE) {
59
- return regionOfLanguage();
60
- }
61
- return region === FORMAT_SYSTEM ? systemLocale() : region;
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 = (onChange) => source.subscribe(onChange);
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 = () => useSyncExternalStore(subscribe, formatLocale, formatLocale);
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
- const hit = store.get(options);
77
- if (hit !== undefined && hit.locale === locale) {
78
- return hit.formatter;
79
- }
80
- const formatter = build(locale, options);
81
- store.set(options, { locale, formatter });
82
- return formatter;
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 = (options) => cacheFor(numbers, options, formatLocale(), (locale, spec) => new Intl.NumberFormat(locale, spec));
89
- export const dateFormatter = (options) => cacheFor(dates, options, formatLocale(), (locale, spec) => new Intl.DateTimeFormat(locale, spec));
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 = (options) => cacheFor(words, options, wordLocale(), (locale, spec) => new Intl.DateTimeFormat(locale, spec));
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 = (options) => cacheFor(relatives, options, wordLocale(), (locale, spec) => new Intl.RelativeTimeFormat(locale, spec));
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
- const locale = wordLocale();
103
- const held = collators.get(locale);
104
- if (held !== undefined) {
105
- return held;
106
- }
107
- const collator = new Intl.Collator(locale, { sensitivity: 'base', numeric: true });
108
- collators.set(locale, collator);
109
- return collator;
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
- nameCollators ??= { loose: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }), exact: new Intl.Collator() };
118
- return nameCollators.loose.compare(left, right) || nameCollators.exact.compare(left, right);
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
+ };