@protobase/ui 0.23.3 → 0.23.5

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 (85) hide show
  1. package/dist/app/app.d.ts +4 -1
  2. package/dist/app/app.js +25 -19
  3. package/dist/app/charts/chart-groups.js +3 -2
  4. package/dist/app/charts/period-chart.js +3 -2
  5. package/dist/app/create/create-values.js +1 -1
  6. package/dist/app/custom-section.js +1 -1
  7. package/dist/app/displays/board-column.js +2 -1
  8. package/dist/app/displays/custom-display.js +1 -1
  9. package/dist/app/displays/display-frame.js +3 -2
  10. package/dist/app/displays/map-display.js +4 -3
  11. package/dist/app/embedded-list/cut-off-note.js +2 -1
  12. package/dist/app/hand-order/hand-sorted-list.js +2 -1
  13. package/dist/app/hand-order/use-hand-order.js +1 -1
  14. package/dist/app/import/import-check-step.js +3 -2
  15. package/dist/app/import/import-counts.js +2 -1
  16. package/dist/app/import/import-dialog.js +2 -1
  17. package/dist/app/import/import-result-step.js +2 -1
  18. package/dist/app/incoming-relations.js +1 -1
  19. package/dist/app/list-edit/use-list-edits.js +1 -1
  20. package/dist/app/list-page.js +1 -1
  21. package/dist/app/main.js +3 -1
  22. package/dist/app/map/records-map.js +4 -3
  23. package/dist/app/meta-gate-messages.js +16 -0
  24. package/dist/app/meta-gate.js +14 -5
  25. package/dist/app/nav-recent.js +1 -1
  26. package/dist/app/pages/blocks/link.js +1 -1
  27. package/dist/app/pages/blocks/progress.js +3 -2
  28. package/dist/app/pages/blocks/stat.js +2 -1
  29. package/dist/app/pages/render-node.js +1 -1
  30. package/dist/app/pages/use-layout-data.js +1 -1
  31. package/dist/app/record-preview.js +1 -1
  32. package/dist/app/record-row/record-row-body.js +2 -1
  33. package/dist/app/saved/saved-records.js +1 -1
  34. package/dist/app/search/messages.js +18 -0
  35. package/dist/app/search/search-model.js +6 -5
  36. package/dist/app/searches/saved-search-rows.js +1 -1
  37. package/dist/app/searches/saved-searches.js +1 -1
  38. package/dist/app/searches/update-search-dialog.js +1 -1
  39. package/dist/app/searches/use-save-search.js +1 -1
  40. package/dist/app-shell/bottom-bar.js +3 -2
  41. package/dist/app-shell/breadcrumb.js +2 -1
  42. package/dist/app-shell/drawer.js +2 -1
  43. package/dist/app-shell/global-search.js +5 -4
  44. package/dist/app-shell/messages.js +64 -0
  45. package/dist/app-shell/profile-menu.js +5 -4
  46. package/dist/app-shell/sidebar-group.js +6 -5
  47. package/dist/app-shell/sidebar.js +4 -3
  48. package/dist/app-shell/theme-switch.js +8 -7
  49. package/dist/app-shell/top-bar.js +5 -4
  50. package/dist/data/api-provider.d.ts +2 -0
  51. package/dist/data/focus-refresh.js +2 -0
  52. package/dist/data/live-refresh.js +29 -0
  53. package/dist/data/meta-data.d.ts +3 -0
  54. package/dist/data/use-live-updates.js +36 -0
  55. package/dist/data/use-meta.js +1 -0
  56. package/dist/data/use-record.js +1 -1
  57. package/dist/data/use-update-record.js +1 -1
  58. package/dist/format/date.js +42 -50
  59. package/dist/format/intl-cache.js +12 -0
  60. package/dist/format/messages.js +24 -0
  61. package/dist/format/money.js +9 -16
  62. package/dist/format/number-text.js +3 -2
  63. package/dist/format/number.js +7 -5
  64. package/dist/format/zone.js +3 -5
  65. package/dist/invitations/pending-invitation-row.js +2 -1
  66. package/dist/live/display-value.js +2 -1
  67. package/dist/locale/active-locale.js +24 -0
  68. package/dist/locale/app-locale.js +18 -0
  69. package/dist/locale/messages.js +12 -0
  70. package/dist/organizations/organizations-page.js +2 -1
  71. package/dist/package.js +1 -1
  72. package/dist/shell/assets/app-Di4_EOe7.js +69 -0
  73. package/dist/shell/assets/export-worker-O1HF9R4j.js +2 -0
  74. package/dist/shell/assets/{index-BShZnsl_.js → index-DIcL_2pR.js} +2 -2
  75. package/dist/shell/assets/{maplibre-Z-0y-JM5.js → maplibre-B8VhXCMb.js} +2 -2
  76. package/dist/shell/assets/{markdown-content-C2__fEXf.js → markdown-content-CDcQeWQm.js} +1 -1
  77. package/dist/shell/assets/{record-map-sG110J_j.js → record-map-Bd0yjDXL.js} +1 -1
  78. package/dist/shell/assets/records-map-QR_MUceu.js +1 -0
  79. package/dist/shell/assets/{shared-protobase-ui-QAWUDrTS.js → shared-protobase-ui-CWBBcLfz.js} +1 -1
  80. package/dist/shell/index.html +3 -3
  81. package/dist/shell/protobase.shell.json +1 -1
  82. package/package.json +4 -4
  83. package/dist/shell/assets/app-DDou93IP.js +0 -69
  84. package/dist/shell/assets/export-worker-C1djGgjD.js +0 -2
  85. package/dist/shell/assets/records-map-mv11CA2m.js +0 -1
@@ -1,77 +1,69 @@
1
+ import { cachedFormat } from "./intl-cache.js";
2
+ import { text } from "./messages.js";
1
3
  import { viewerZone } from "./zone.js";
2
4
  //#region src/format/date.ts
3
- const day = new Intl.DateTimeFormat("en-GB", {
5
+ const dateFormat = (name, options) => () => cachedFormat(`date:${name}`, (tag) => new Intl.DateTimeFormat(tag, {
6
+ ...options,
7
+ timeZone: "UTC"
8
+ }));
9
+ const day = dateFormat("day", {
4
10
  day: "numeric",
5
11
  month: "short",
6
- year: "numeric",
7
- timeZone: "UTC"
12
+ year: "numeric"
8
13
  });
9
- const dayShort = new Intl.DateTimeFormat("en-GB", {
14
+ const dayShort = dateFormat("dayShort", {
10
15
  day: "numeric",
11
- month: "short",
12
- timeZone: "UTC"
16
+ month: "short"
13
17
  });
14
- const month = new Intl.DateTimeFormat("en-GB", {
18
+ const month = dateFormat("month", {
15
19
  month: "long",
16
- year: "numeric",
17
- timeZone: "UTC"
20
+ year: "numeric"
18
21
  });
19
- const weekday = new Intl.DateTimeFormat("en-GB", {
20
- weekday: "short",
21
- timeZone: "UTC"
22
- });
23
- const weekdayDate = new Intl.DateTimeFormat("en-GB", {
22
+ const weekday = dateFormat("weekday", { weekday: "short" });
23
+ const weekdayDate = dateFormat("weekdayDate", {
24
24
  weekday: "long",
25
25
  day: "numeric",
26
26
  month: "short",
27
- year: "numeric",
28
- timeZone: "UTC"
27
+ year: "numeric"
29
28
  });
30
- const formatDate = (ms) => day.format(ms);
31
- const formatDayShort = (ms) => dayShort.format(ms);
32
- const zoned = /* @__PURE__ */ new Map();
33
- const zonedFormats = (zone) => {
34
- const known = zoned.get(zone);
35
- if (known) return known;
36
- const formats = {
37
- day: new Intl.DateTimeFormat("en-GB", {
38
- day: "numeric",
39
- month: "short",
40
- year: "numeric",
41
- timeZone: zone
42
- }),
43
- time: new Intl.DateTimeFormat("en-GB", {
44
- hour: "2-digit",
45
- minute: "2-digit",
46
- hour12: false,
47
- timeZone: zone
48
- })
49
- };
50
- zoned.set(zone, formats);
51
- return formats;
52
- };
29
+ const formatDate = (ms) => day().format(ms);
30
+ const formatDayShort = (ms) => dayShort().format(ms);
31
+ const zonedFormats = (zone) => cachedFormat(`zoned:${zone}`, (tag) => ({
32
+ day: new Intl.DateTimeFormat(tag, {
33
+ day: "numeric",
34
+ month: "short",
35
+ year: "numeric",
36
+ timeZone: zone
37
+ }),
38
+ time: new Intl.DateTimeFormat(tag, {
39
+ hour: "2-digit",
40
+ minute: "2-digit",
41
+ hour12: false,
42
+ timeZone: zone
43
+ })
44
+ }));
53
45
  /** "6 Oct 2026, 16:32": an instant on the clock of `zone`, the viewer's unless given. */
54
46
  const formatDateTime = (ms, zone = viewerZone()) => {
55
47
  const formats = zonedFormats(zone);
56
48
  return `${formats.day.format(ms)}, ${formats.time.format(ms)}`;
57
49
  };
58
50
  /** "October 2026". */
59
- const formatMonth = (ms) => month.format(ms);
51
+ const formatMonth = (ms) => month().format(ms);
60
52
  /** "Mon". */
61
- const formatWeekday = (ms) => weekday.format(ms);
53
+ const formatWeekday = (ms) => weekday().format(ms);
62
54
  /** "Monday, 12 Oct 2026". */
63
- const formatWeekdayDate = (ms) => weekdayDate.format(ms);
55
+ const formatWeekdayDate = (ms) => weekdayDate().format(ms);
64
56
  /** "12–18 Oct 2026", "28 Dec 2026 – 3 Jan 2027": the days from `from` to `to`, both included. */
65
- const formatDayRange = (from, to) => day.formatRange(from, to);
57
+ const formatDayRange = (from, to) => day().formatRange(from, to);
66
58
  /** "09:30": an instant's time on the clock of `zone`, the viewer's unless given. */
67
59
  const formatTime = (ms, zone = viewerZone()) => zonedFormats(zone).time.format(ms);
68
60
  const formatAgo = (seconds) => {
69
- if (seconds < 60) return `${seconds} s ago`;
70
- if (seconds < 3600) return `${Math.floor(seconds / 60)} min ago`;
71
- if (seconds < 86400) return `${Math.floor(seconds / 3600)} h ago`;
72
- return `${Math.floor(seconds / 86400)} d ago`;
61
+ if (seconds < 60) return text("secondsAgo", seconds);
62
+ if (seconds < 3600) return text("minutesAgo", Math.floor(seconds / 60));
63
+ if (seconds < 86400) return text("hoursAgo", Math.floor(seconds / 3600));
64
+ return text("daysAgo", Math.floor(seconds / 86400));
73
65
  };
74
- const relative = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
66
+ const relative = () => cachedFormat("relative", (tag) => new Intl.RelativeTimeFormat(tag, { numeric: "auto" }));
75
67
  const units = [
76
68
  ["year", 31536e3],
77
69
  ["month", 2592e3],
@@ -83,8 +75,8 @@ const units = [
83
75
  const formatRelativeTime = (seconds) => {
84
76
  const away = Math.abs(seconds);
85
77
  const [unit, size] = units.find(([, size]) => away >= size) ?? ["second", 1];
86
- if (unit === "second" && away < 45) return "just now";
87
- return relative.format(-Math.sign(seconds) * Math.floor(away / size), unit);
78
+ if (unit === "second" && away < 45) return text("justNow");
79
+ return relative().format(-Math.sign(seconds) * Math.floor(away / size), unit);
88
80
  };
89
81
  //#endregion
90
82
  export { formatAgo, formatDate, formatDateTime, formatDayRange, formatDayShort, formatMonth, formatRelativeTime, formatTime, formatWeekday, formatWeekdayDate };
@@ -0,0 +1,12 @@
1
+ import { formatTag } from "../locale/active-locale.js";
2
+ //#region src/format/intl-cache.ts
3
+ const cache = /* @__PURE__ */ new Map();
4
+ /** One `Intl` formatter per language tag and `key`, made with `make` for the active locale's tag the first time. */
5
+ const cachedFormat = (key, make) => {
6
+ const tag = formatTag();
7
+ const id = `${tag}|${key}`;
8
+ if (!cache.has(id)) cache.set(id, make(tag));
9
+ return cache.get(id);
10
+ };
11
+ //#endregion
12
+ export { cachedFormat };
@@ -0,0 +1,24 @@
1
+ import { defineMessages } from "../locale/messages.js";
2
+ //#region src/format/messages.ts
3
+ const text = defineMessages({
4
+ en: {
5
+ rows: (count) => `${count} rows`,
6
+ aboutRows: (count) => `about ${count} rows`,
7
+ secondsAgo: (count) => `${count} s ago`,
8
+ minutesAgo: (count) => `${count} min ago`,
9
+ hoursAgo: (count) => `${count} h ago`,
10
+ daysAgo: (count) => `${count} d ago`,
11
+ justNow: "just now"
12
+ },
13
+ nl: {
14
+ rows: (count) => `${count} rijen`,
15
+ aboutRows: (count) => `ongeveer ${count} rijen`,
16
+ secondsAgo: (count) => `${count} s geleden`,
17
+ minutesAgo: (count) => `${count} min geleden`,
18
+ hoursAgo: (count) => `${count} u geleden`,
19
+ daysAgo: (count) => `${count} d geleden`,
20
+ justNow: "zojuist"
21
+ }
22
+ });
23
+ //#endregion
24
+ export { text };
@@ -1,20 +1,13 @@
1
+ import { cachedFormat } from "./intl-cache.js";
1
2
  //#region src/format/money.ts
2
- const formatters = /* @__PURE__ */ new Map();
3
- const formatterFor = (currency, decimals) => {
4
- const key = `${currency}:${decimals ?? ""}`;
5
- const cached = formatters.get(key);
6
- if (cached) return cached;
7
- const created = new Intl.NumberFormat("en-IE", {
8
- style: "currency",
9
- currency,
10
- ...decimals !== void 0 && {
11
- minimumFractionDigits: decimals,
12
- maximumFractionDigits: decimals
13
- }
14
- });
15
- formatters.set(key, created);
16
- return created;
17
- };
3
+ const formatterFor = (currency, decimals) => cachedFormat(`money:${currency}:${decimals ?? ""}`, (tag) => new Intl.NumberFormat(tag, {
4
+ style: "currency",
5
+ currency,
6
+ ...decimals !== void 0 && {
7
+ minimumFractionDigits: decimals,
8
+ maximumFractionDigits: decimals
9
+ }
10
+ }));
18
11
  /** An amount in cents in the currency, with its usual decimals or `decimals` of them. */
19
12
  const formatMoney = (cents, currency = "EUR", decimals) => formatterFor(currency, decimals).format(cents / 100);
20
13
  //#endregion
@@ -1,6 +1,7 @@
1
+ import { formatTag } from "../locale/active-locale.js";
1
2
  //#region src/format/number-text.ts
2
- /** The decimal separator `locale` writes, the browser's language unless given: `,` for nl-NL and de-DE, `.` for en-GB. */
3
- const localeDecimal = (locale) => new Intl.NumberFormat(locale).formatToParts(1.5).find((part) => part.type === "decimal")?.value === "," ? "," : ".";
3
+ /** The decimal separator `locale` writes, the screens' unless given: `,` for nl-NL and de-DE, `.` for en-GB. */
4
+ const localeDecimal = (locale = formatTag()) => new Intl.NumberFormat(locale).formatToParts(1.5).find((part) => part.type === "decimal")?.value === "," ? "," : ".";
4
5
  const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
6
  /**
6
7
  * A number written as text, as a plain decimal (`-1234.5`): digits grouped by thousands with the other separator or
@@ -1,12 +1,14 @@
1
+ import { cachedFormat } from "./intl-cache.js";
2
+ import { text } from "./messages.js";
1
3
  //#region src/format/number.ts
2
- const grouped = new Intl.NumberFormat("en-IE");
3
- const formatInt = (value) => grouped.format(value);
4
- const formatSigned = (value) => value > 0 ? `+${grouped.format(value)}` : grouped.format(value).replace("-", "−");
4
+ const grouped = () => cachedFormat("grouped", (tag) => new Intl.NumberFormat(tag));
5
+ const formatInt = (value) => grouped().format(value);
6
+ const formatSigned = (value) => value > 0 ? `+${grouped().format(value)}` : grouped().format(value).replace("-", "−");
5
7
  const formatPercent = (value) => `${value}%`;
6
8
  const formatApproxRows = (count) => {
7
- if (count < 1e4) return `${formatInt(count)} rows`;
9
+ if (count < 1e4) return text("rows", formatInt(count));
8
10
  const magnitude = 10 ** (Math.floor(Math.log10(count)) - 3);
9
- return `about ${formatInt(Math.round(count / magnitude) * magnitude)} rows`;
11
+ return text("aboutRows", formatInt(Math.round(count / magnitude) * magnitude));
10
12
  };
11
13
  //#endregion
12
14
  export { formatApproxRows, formatInt, formatPercent, formatSigned };
@@ -1,3 +1,4 @@
1
+ import { cachedFormat } from "./intl-cache.js";
1
2
  //#region src/format/zone.ts
2
3
  /** The viewer's IANA time zone, such as `Europe/Amsterdam`. */
3
4
  const viewerZone = () => Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -47,15 +48,12 @@ const instantAt = (wall, zone) => {
47
48
  };
48
49
  /** The instant `day` (`YYYY-MM-DD`) starts in `zone`: its local midnight. */
49
50
  const startOfDayIn = (day, zone) => instantAt(Date.parse(`${day}T00:00:00Z`), zone);
50
- const zoneNames = /* @__PURE__ */ new Map();
51
51
  /** The zone a time is shown in, as its hover label says: "Europe/Amsterdam (GMT+2)", or "UTC". */
52
52
  const zoneLabel = (ms, zone) => {
53
- const known = zoneNames.get(zone) ?? new Intl.DateTimeFormat("en-GB", {
53
+ const name = cachedFormat(`zoneName:${zone}`, (tag) => new Intl.DateTimeFormat(tag, {
54
54
  timeZone: zone,
55
55
  timeZoneName: "shortOffset"
56
- });
57
- zoneNames.set(zone, known);
58
- const name = known.formatToParts(ms).find((part) => part.type === "timeZoneName")?.value ?? zone;
56
+ })).formatToParts(ms).find((part) => part.type === "timeZoneName")?.value ?? zone;
59
57
  return zone === "UTC" || zone === "Etc/UTC" ? "UTC" : `${zone} (${name})`;
60
58
  };
61
59
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { formatTag } from "../locale/active-locale.js";
1
2
  import { Button } from "../primitives/button.js";
2
3
  import { SectionError } from "../account/account-section.js";
3
4
  import { useAction } from "../organizations/use-action.js";
@@ -26,7 +27,7 @@ const PendingInvitationRow = ({ email, gives, expiresAt, onResend, onRevoke }) =
26
27
  children: [
27
28
  gives,
28
29
  " · ",
29
- expired ? "Expired" : `Until ${new Date(expiresAt).toLocaleDateString()}`
30
+ expired ? "Expired" : `Until ${new Date(expiresAt).toLocaleDateString(formatTag())}`
30
31
  ]
31
32
  })]
32
33
  }),
@@ -1,3 +1,4 @@
1
+ import { formatTag } from "../locale/active-locale.js";
1
2
  import { viewerZone, zoneLabel } from "../format/zone.js";
2
3
  import { formatDate, formatDateTime, formatRelativeTime } from "../format/date.js";
3
4
  import { fileShown } from "./file-values.js";
@@ -22,7 +23,7 @@ const displayValue = (field, value, hints = {}, zone = viewerZone(), now = Date.
22
23
  case "decimal": {
23
24
  const text = hints.decimals === void 0 ? String(value) : Number(value).toFixed(hints.decimals);
24
25
  if (hints.format === "percent") return `${Number(value)}%`;
25
- return affixed(Number(text).toLocaleString("en-IE", {
26
+ return affixed(Number(text).toLocaleString(formatTag(), {
26
27
  minimumFractionDigits: hints.decimals ?? 0,
27
28
  maximumFractionDigits: hints.decimals ?? 20
28
29
  }), hints);
@@ -0,0 +1,24 @@
1
+ import { pickLocale } from "@protobase/schema/internal";
2
+ //#region src/locale/active-locale.ts
3
+ let fallback = "en";
4
+ let app;
5
+ /** The language the screens are in now. */
6
+ const activeLocale = () => app ?? fallback;
7
+ /** The language the screens are in when the app sets none: the browser's, or the one the app was given. */
8
+ const setFallbackLocale = (locale) => {
9
+ fallback = locale;
10
+ };
11
+ /** The language the app sets for everyone, from its `/meta`; undefined when it sets none. */
12
+ const setAppLocale = (locale) => {
13
+ app = locale;
14
+ };
15
+ /** The browser's language when the screens come in it, else English. */
16
+ const browserLocale = (languages = globalThis.navigator?.languages ?? []) => pickLocale(languages);
17
+ const formatTags = {
18
+ en: "en-GB",
19
+ nl: "nl-NL"
20
+ };
21
+ /** The language tag `Intl` formats numbers and dates with, for the active locale. */
22
+ const formatTag = () => formatTags[activeLocale()];
23
+ //#endregion
24
+ export { activeLocale, browserLocale, formatTag, setAppLocale, setFallbackLocale };
@@ -0,0 +1,18 @@
1
+ import { activeLocale, setAppLocale } from "./active-locale.js";
2
+ import { Fragment, useEffect } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/locale/app-locale.tsx
5
+ /**
6
+ * Puts the screens below in the language the app sets for everyone, from its `/meta`, or in the fallback when it sets
7
+ * none; draws them anew when that changes, and tells the page which language it is in.
8
+ */
9
+ const AppLocale = ({ locale, children }) => {
10
+ setAppLocale(locale);
11
+ const active = activeLocale();
12
+ useEffect(() => {
13
+ document.documentElement.lang = active;
14
+ }, [active]);
15
+ return /* @__PURE__ */ jsx(Fragment, { children }, active);
16
+ };
17
+ //#endregion
18
+ export { AppLocale };
@@ -0,0 +1,12 @@
1
+ import { activeLocale } from "./active-locale.js";
2
+ //#region src/locale/messages.ts
3
+ /**
4
+ * An area's built-in texts in every language the screens come in, keyed by id: the lookup returns the one for the
5
+ * active locale, `text('search')` or `text('rows', 3)`. Dutch must translate every English text, which the types check.
6
+ */
7
+ const defineMessages = (catalogue) => (id, ...values) => {
8
+ const message = catalogue[activeLocale()][id];
9
+ return typeof message === "function" ? message(...values) : message;
10
+ };
11
+ //#endregion
12
+ export { defineMessages };
@@ -1,3 +1,4 @@
1
+ import { formatTag } from "../locale/active-locale.js";
1
2
  import { Input } from "../primitives/input.js";
2
3
  import { PageHeader } from "../app-shell/page-header.js";
3
4
  import { useAuth } from "../auth/auth-provider.js";
@@ -85,7 +86,7 @@ const YourInvitations = () => {
85
86
  invitation.appRoles.map((name) => roleLabel(roles, name)).join(", ") || "no app roles",
86
87
  /* @__PURE__ */ jsxs("span", {
87
88
  className: "block text-xs text-muted-foreground",
88
- children: ["Until ", new Date(invitation.expiresAt).toLocaleDateString()]
89
+ children: ["Until ", new Date(invitation.expiresAt).toLocaleDateString(formatTag())]
89
90
  })
90
91
  ]
91
92
  }, invitation.id))
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.23.3";
2
+ var version = "0.23.5";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };