@eifi1/ui-kit 0.46.0 → 0.47.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 (148) hide show
  1. package/README.md +12 -11
  2. package/dist/admin/user-roster.d.ts +6 -2
  3. package/dist/admin/user-roster.js +7 -3
  4. package/dist/admin/user-roster.js.map +1 -1
  5. package/dist/components/data-table-row-actions.d.ts +22 -5
  6. package/dist/components/data-table-row-actions.js +14 -5
  7. package/dist/components/data-table-row-actions.js.map +1 -1
  8. package/dist/components/data-table.d.ts +16 -1
  9. package/dist/components/data-table.js +20 -1
  10. package/dist/components/data-table.js.map +1 -1
  11. package/dist/components/row-actions.d.ts +37 -5
  12. package/dist/components/row-actions.js +22 -5
  13. package/dist/components/row-actions.js.map +1 -1
  14. package/dist/components/tabs.d.ts +24 -16
  15. package/dist/components/tabs.js +37 -31
  16. package/dist/components/tabs.js.map +1 -1
  17. package/dist/components/translation-review.js +3 -1
  18. package/dist/components/translation-review.js.map +1 -1
  19. package/dist/components/ui-labels.js +1 -1
  20. package/dist/components/ui-labels.js.map +1 -1
  21. package/dist/data-table.d.ts +1 -0
  22. package/dist/data-table.js +2 -0
  23. package/dist/data-table.js.map +1 -1
  24. package/dist/eslint/index.js +1 -1
  25. package/dist/feedback/feedback-attachment-download.d.ts +19 -0
  26. package/dist/feedback/feedback-attachment-download.js +59 -0
  27. package/dist/feedback/feedback-attachment-download.js.map +1 -0
  28. package/dist/feedback/feedback-comment-thread.d.ts +17 -6
  29. package/dist/feedback/feedback-comment-thread.js +21 -2
  30. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  31. package/dist/feedback/feedback-entry-file.d.ts +61 -0
  32. package/dist/feedback/feedback-entry-file.js +76 -0
  33. package/dist/feedback/feedback-entry-file.js.map +1 -0
  34. package/dist/feedback/feedback-public-list.d.ts +18 -5
  35. package/dist/feedback/feedback-public-list.js +8 -0
  36. package/dist/feedback/feedback-public-list.js.map +1 -1
  37. package/dist/feedback/feedback-public-types.d.ts +7 -2
  38. package/dist/feedback/feedback-replies.d.ts +26 -9
  39. package/dist/feedback/feedback-replies.js +20 -10
  40. package/dist/feedback/feedback-replies.js.map +1 -1
  41. package/dist/feedback/feedback-row-detail-labels.js +0 -1
  42. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  43. package/dist/feedback/feedback-row-detail.d.ts +23 -10
  44. package/dist/feedback/feedback-row-detail.js +10 -54
  45. package/dist/feedback/feedback-row-detail.js.map +1 -1
  46. package/dist/feedback.d.ts +1 -0
  47. package/dist/feedback.js +1 -0
  48. package/dist/feedback.js.map +1 -1
  49. package/dist/i18n/german.js +1 -6
  50. package/dist/i18n/german.js.map +1 -1
  51. package/dist/i18n/locales/en.js +1 -6
  52. package/dist/i18n/locales/en.js.map +2 -2
  53. package/dist/i18n/locales/es.js +1 -6
  54. package/dist/i18n/locales/es.js.map +1 -1
  55. package/dist/i18n/locales/fr.js +1 -6
  56. package/dist/i18n/locales/fr.js.map +1 -1
  57. package/dist/i18n/locales/hu.js +1 -6
  58. package/dist/i18n/locales/hu.js.map +1 -1
  59. package/dist/i18n/locales/it.js +1 -6
  60. package/dist/i18n/locales/it.js.map +1 -1
  61. package/dist/i18n/locales/zh.js +1 -6
  62. package/dist/i18n/locales/zh.js.map +1 -1
  63. package/dist/i18n/review.js +0 -1
  64. package/dist/i18n/review.js.map +1 -1
  65. package/dist/index.d.ts +5 -7
  66. package/dist/index.js +14 -5
  67. package/dist/index.js.map +1 -1
  68. package/dist/notifications/device-zone-report.d.ts +7 -0
  69. package/dist/notifications/device-zone-report.js +8 -1
  70. package/dist/notifications/device-zone-report.js.map +1 -1
  71. package/dist/notifications/feedback-notification-types.d.ts +14 -1
  72. package/dist/notifications/feedback-notification-types.js +5 -1
  73. package/dist/notifications/feedback-notification-types.js.map +1 -1
  74. package/dist/notifications/notification-bell.d.ts +1 -1
  75. package/dist/notifications/notification-bell.js.map +1 -1
  76. package/dist/notifications/notification-inbox.d.ts +12 -3
  77. package/dist/notifications/notification-inbox.js +2 -2
  78. package/dist/notifications/notification-inbox.js.map +1 -1
  79. package/dist/notifications/notification-types.d.ts +0 -3
  80. package/dist/notifications/notifications-labels.d.ts +2 -13
  81. package/dist/notifications/notifications-labels.js +0 -4
  82. package/dist/notifications/notifications-labels.js.map +1 -1
  83. package/dist/notifications/quiet-hours-setting.d.ts +3 -8
  84. package/dist/notifications/quiet-hours-setting.js +8 -3
  85. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  86. package/dist/offline/local-first.d.ts +9 -0
  87. package/dist/offline/local-first.js +14 -0
  88. package/dist/offline/local-first.js.map +1 -1
  89. package/dist/offline.d.ts +1 -1
  90. package/dist/offline.js +2 -1
  91. package/dist/offline.js.map +1 -1
  92. package/dist/sync/db.d.ts +17 -0
  93. package/dist/sync/db.js +10 -1
  94. package/dist/sync/db.js.map +1 -1
  95. package/dist/sync/engine.d.ts +11 -9
  96. package/dist/sync/engine.js.map +1 -1
  97. package/dist/sync/hooks.d.ts +5 -0
  98. package/dist/sync/hooks.js.map +1 -1
  99. package/dist/sync.d.ts +2 -2
  100. package/dist/sync.js +5 -2
  101. package/dist/sync.js.map +1 -1
  102. package/dist/theme/theme-store.d.ts +4 -4
  103. package/dist/theme/theme-store.js +1 -0
  104. package/dist/theme/theme-store.js.map +1 -1
  105. package/package.json +2 -2
  106. package/src/admin/user-roster.tsx +12 -3
  107. package/src/components/data-table-row-actions.tsx +33 -9
  108. package/src/components/data-table.tsx +46 -3
  109. package/src/components/row-actions.tsx +73 -10
  110. package/src/components/tabs.tsx +66 -44
  111. package/src/components/translation-review.tsx +3 -1
  112. package/src/components/ui-labels.ts +1 -1
  113. package/src/data-table.ts +3 -0
  114. package/src/feedback/feedback-attachment-download.tsx +77 -0
  115. package/src/feedback/feedback-comment-thread.tsx +42 -7
  116. package/src/feedback/feedback-entry-file.tsx +150 -0
  117. package/src/feedback/feedback-public-list.tsx +25 -4
  118. package/src/feedback/feedback-public-types.ts +7 -2
  119. package/src/feedback/feedback-replies.tsx +46 -19
  120. package/src/feedback/feedback-row-detail-labels.ts +0 -1
  121. package/src/feedback/feedback-row-detail.tsx +35 -86
  122. package/src/feedback.ts +1 -0
  123. package/src/i18n/german.ts +1 -6
  124. package/src/i18n/locales/es.ts +1 -6
  125. package/src/i18n/locales/fr.ts +1 -6
  126. package/src/i18n/locales/hu.ts +1 -6
  127. package/src/i18n/locales/it.ts +1 -6
  128. package/src/i18n/locales/zh.ts +1 -6
  129. package/src/i18n/review.ts +0 -1
  130. package/src/index.ts +19 -5
  131. package/src/notifications/device-zone-report.ts +14 -1
  132. package/src/notifications/feedback-notification-types.ts +19 -1
  133. package/src/notifications/notification-bell.tsx +1 -1
  134. package/src/notifications/notification-inbox.tsx +15 -5
  135. package/src/notifications/notification-types.ts +0 -3
  136. package/src/notifications/notifications-labels.ts +2 -17
  137. package/src/notifications/quiet-hours-setting.tsx +16 -11
  138. package/src/offline/local-first.ts +24 -0
  139. package/src/offline.ts +1 -1
  140. package/src/sync/db.ts +22 -0
  141. package/src/sync/engine.ts +11 -9
  142. package/src/sync/hooks.ts +5 -0
  143. package/src/sync.ts +2 -2
  144. package/src/theme/theme-store.ts +13 -5
  145. package/dist/notifications/time-zone-select.d.ts +0 -53
  146. package/dist/notifications/time-zone-select.js +0 -76
  147. package/dist/notifications/time-zone-select.js.map +0 -1
  148. package/src/notifications/time-zone-select.tsx +0 -129
@@ -40,12 +40,13 @@ export interface SyncStepResult {
40
40
  counts?: Partial<SyncCounts>;
41
41
  }
42
42
 
43
- /** Work after the push/pull, such as kastlan's photo upload. */
44
- export interface SyncStep {
43
+ /** Work after the push/pull, such as kastlan's photo upload. `D` is the app's database type
44
+ * (`createSyncDb<HandoverDb>`), so a step reaches the app's own tables without a cast. */
45
+ export interface SyncStep<D extends SyncDb = SyncDb> {
45
46
  name: string;
46
- run(db: SyncDb, ctx: SyncStepContext): Promise<SyncStepResult>;
47
+ run(db: D, ctx: SyncStepContext): Promise<SyncStepResult>;
47
48
  /** Its counts read from the device between rounds (a photo taken offline shows at once). */
48
- counts?(db: SyncDb): Promise<Partial<SyncCounts>>;
49
+ counts?(db: D): Promise<Partial<SyncCounts>>;
49
50
  }
50
51
 
51
52
  export interface SyncEngineState {
@@ -68,8 +69,9 @@ export interface SyncRoundResult {
68
69
  error?: unknown;
69
70
  }
70
71
 
71
- export interface SyncEngineOptions {
72
- db: SyncDb;
72
+ export interface SyncEngineOptions<D extends SyncDb = SyncDb> {
73
+ /** The database; its type `D` is the one the `steps` get. */
74
+ db: D;
73
75
  /** The scopes this session's role holds, in round order (kastlan: handover, meetings, meters,
74
76
  * maintenance, contacts). Read at the start of every round: roles are per company. */
75
77
  scopes: () => readonly string[];
@@ -85,7 +87,7 @@ export interface SyncEngineOptions {
85
87
  holdCodes?: readonly string[];
86
88
  /** Ask for a paged pull and follow its cursor (D12). */
87
89
  paged?: boolean;
88
- steps?: readonly SyncStep[];
90
+ steps?: readonly SyncStep<D>[];
89
91
  /** No round while this holds (kastlan: a practice tour fakes the server's answers). */
90
92
  paused?: () => boolean;
91
93
  /** The poll (30 s) and the debounce after a write (800 ms). */
@@ -186,7 +188,7 @@ const toWire = ({ entity, id, op, data, updated_at }: OutboxChange): SyncWireCha
186
188
  * engine on the new database and stops the old one). Nothing runs until `start()`
187
189
  * (`useSyncWorker`), and `start()` does nothing on a computer marked shared.
188
190
  */
189
- export function createSyncEngine(options: SyncEngineOptions): SyncEngine {
191
+ export function createSyncEngine<D extends SyncDb = SyncDb>(options: SyncEngineOptions<D>): SyncEngine {
190
192
  const { db } = options;
191
193
  const isOffline = options.isOffline ?? isOfflineError;
192
194
  const planCode = options.planCode ?? defaultPlanCode;
@@ -674,7 +676,7 @@ export function createSyncEngine(options: SyncEngineOptions): SyncEngine {
674
676
  * changing values (scopes, the client) through functions. A database object opened afresh
675
677
  * under the same name replaces the kept engine (the old one is stopped).
676
678
  */
677
- export function syncEngineFor(options: SyncEngineOptions): SyncEngine {
679
+ export function syncEngineFor<D extends SyncDb = SyncDb>(options: SyncEngineOptions<D>): SyncEngine {
678
680
  const name = options.db.name;
679
681
  const found = kept.get(name);
680
682
  if (found && found.db === options.db) return found.engine;
package/src/sync/hooks.ts CHANGED
@@ -33,6 +33,11 @@ const idle = () => IDLE;
33
33
  * and in flight (and the steps' pending), `waitingForPlan` = `waiting`, `blocked` = `blocked`
34
34
  * + `rejected`, `lastSyncedAt` from `meta`. `onSync` is "Sync now". Without an engine, an
35
35
  * idle status that follows the browser's connection.
36
+ *
37
+ * With a `null` engine (signed out, or a computer marked shared) `status.online` follows
38
+ * `navigator.onLine` and its `online`/`offline` events, so an app may lean on
39
+ * `status.online` for its own offline lock (kastlan's offline write lock) whether or not an
40
+ * engine runs.
36
41
  */
37
42
  export function useSyncStatus(engine: SyncEngine | null): { status: SyncStatus; onSync: () => Promise<void> } {
38
43
  const state = useSyncExternalStore(
package/src/sync.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  // The only entry point that imports `dexie` — an optional peer for that reason. It never loads
5
5
  // `/offline`'s TanStack and idb-keyval peers: the leaf utilities both entries export live in
6
6
  // one module of their own (src/offline/local-first.ts). NOT re-exported from the main barrel.
7
- export { createSyncDb, SYNC_DB_STORES } from "./sync/db";
7
+ export { createSyncDb, rowByPk, SYNC_DB_PK_INDEX, SYNC_DB_STORES } from "./sync/db";
8
8
  export type { CreateSyncDbOptions, OutboxChange, OutboxStatus, SyncDb, SyncDbUpgrade, SyncedRow, SyncMetaEntry } from "./sync/db";
9
9
  export { createSyncEngine, DEFAULT_HOLD_CODES, DEFAULT_SYNC_BACKOFF_MS, releaseSyncEngine, syncEngineFor } from "./sync/engine";
10
10
  export type {
@@ -26,4 +26,4 @@ export type {
26
26
  SyncResponseBody,
27
27
  SyncWireChange,
28
28
  } from "./sync/wire";
29
- export { isOfflineError, readLocalFirst, stampAfter, writeLocalFirst } from "./offline/local-first";
29
+ export { isOfflineError, readLocalFirst, stampAfter, uuid5, writeLocalFirst } from "./offline/local-first";
@@ -47,20 +47,28 @@ function preferenceFromState(
47
47
  return "system";
48
48
  }
49
49
 
50
- /** Toggle the `.dark` class on <html> for a resolved light/dark mode. */
50
+ /**
51
+ * Toggle the `.dark` class on <html> for a resolved light/dark mode, and say the same
52
+ * to the BROWSER through `color-scheme` (0.47, keksdose dev #600): the class switches
53
+ * the kit's tokens, but everything the browser paints itself — a native `<select>`'s
54
+ * open list, a date input's popup, scrollbars, autofill — follows `color-scheme` only,
55
+ * and stayed light on the dark theme. Inline on <html>, so it holds whatever an app's
56
+ * stylesheet says, and it is set by the pre-paint {@link applyPersistedTheme} too.
57
+ */
51
58
  function applyThemeClass(mode: ThemeMode): void {
52
59
  const root = document.documentElement;
53
60
  if (mode === "dark") root.classList.add("dark");
54
61
  else root.classList.remove("dark");
62
+ root.style.colorScheme = mode;
55
63
  }
56
64
 
57
65
  /**
58
66
  * Pre-hydration: read the theme PREFERENCE persisted under `storageKey` straight
59
67
  * from localStorage, resolve it to a concrete light/dark mode, and toggle the
60
- * `.dark` class on <html> — BEFORE React and the store mount, so the first paint
61
- * already has the right theme (no flash). Returns the resolved mode so the caller
62
- * can chain {@link applyPersistedPalette}. Encapsulates the zustand-persist
63
- * envelope + the legacy `mode`→`preference` fallback so apps never re-type the
68
+ * `.dark` class (and `color-scheme`) on <html> — BEFORE React and the store mount,
69
+ * so the first paint already has the right theme (no flash). Returns the resolved
70
+ * mode so the caller can chain {@link applyPersistedPalette}. Encapsulates the
71
+ * zustand-persist envelope + the legacy `mode`→`preference` fallback so apps never re-type the
64
72
  * storage shape in their entrypoint.
65
73
  */
66
74
  export function applyPersistedTheme(storageKey: string): ThemeMode {
@@ -1,53 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import type { LabelOverride } from "../i18n/kit-labels.js";
3
- import type { ComboOption } from "../components/combobox-core.js";
4
- import type { CommitScope } from "../components/write-lock.js";
5
- import type { NotificationRegionLabels, NotificationsLabels } from "./notifications-labels.js";
6
- /**
7
- * This device's IANA zone ("Europe/Zurich"), or `"UTC"` where the runtime names none —
8
- * the zone `useDeviceZoneReport` reports, which becomes the account's (§10.3, automatic
9
- * only).
10
- */
11
- export declare function deviceTimeZone(): string;
12
- /**
13
- * The zone picker's rows (keksdose `notifications-card.tsx:131-146`): every zone `Intl`
14
- * lists, as its city ("Argentina / Buenos Aires") under its region, the region named by
15
- * `regionLabel` — which answers `undefined` for a region it does not know, and the region
16
- * keeps its IANA name. A zone with no region ("UTC") comes first, ahead of every heading,
17
- * rather than trailing the last group as if it were Pacific. `current` is kept even when
18
- * the runtime does not list it (an old alias such as "Europe/Kiev"): a picker without its
19
- * own value would show an empty field.
20
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
21
- */
22
- export declare function timeZoneOptions(current: string, regionLabel: (region: string) => string | undefined): ComboOption<string>[];
23
- /** The region's name in `regions`, or `undefined` for one the labels do not know.
24
- * @internal Not part of the barrel. */
25
- export declare function regionName(regions: NotificationRegionLabels, region: string): string | undefined;
26
- /** A zone as the picker shows it, for a sentence: "Europe / Zurich", the region in the
27
- * kit's words. @internal Not part of the barrel. */
28
- export declare function zoneName(zone: string, regions: NotificationRegionLabels): string;
29
- /** @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47. */
30
- export interface TimeZoneSelectProps {
31
- /** The IANA zone. Not clearable: a zone can be changed, never emptied. */
32
- value: string;
33
- /** The zone picked. Never called for the value it already has. */
34
- onChange: (zone: string) => void;
35
- /** Default `labels.timeZone`. */
36
- label?: ReactNode;
37
- disabled?: boolean;
38
- /** The input's id. */
39
- id?: string;
40
- className?: string;
41
- /** The picker SAVES on change (quiet hours do): locked under a write lock in scope. */
42
- commit?: CommitScope;
43
- labels?: LabelOverride<NotificationsLabels>;
44
- }
45
- /**
46
- * An IANA time zone, searchable (docs/notifications-harmonization.md §4.2, keksdose's
47
- * quiet-hours picker): type "Zur" for Europe/Zurich, and on a phone the kit's sheet with
48
- * a search box instead of a 400-row wheel. Rows are the city under its region, the region
49
- * in the kit's words (`labels.regions`); a zone with no region first; a stored zone the
50
- * runtime does not list is kept. On the kit's {@link InlineEntityCombobox}.
51
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
52
- */
53
- export declare function TimeZoneSelect({ value, onChange, label, disabled, id, className, commit, labels: labelsProp, }: TimeZoneSelectProps): import("react").JSX.Element;
@@ -1,76 +0,0 @@
1
- "use client";
2
- import { jsx } from "react/jsx-runtime";
3
- import { useMemo } from "react";
4
- import { InlineEntityCombobox } from "../components/inline-entity-combobox.js";
5
- import { useNotificationsLabels } from "./notification-parts.js";
6
- function deviceTimeZone() {
7
- try {
8
- return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
9
- } catch {
10
- return "UTC";
11
- }
12
- }
13
- function runtimeZones() {
14
- const intl = Intl;
15
- return typeof intl.supportedValuesOf === "function" ? intl.supportedValuesOf("timeZone") : [];
16
- }
17
- function cityOf(rest) {
18
- return rest.replace(/_/g, " ").replace(/\//g, " / ");
19
- }
20
- function timeZoneOptions(current, regionLabel) {
21
- const zones = runtimeZones();
22
- const all = !current || zones.includes(current) ? zones : [...zones, current].sort();
23
- const rows = all.map((zone) => {
24
- const slash = zone.indexOf("/");
25
- if (slash < 0) return { value: zone, label: zone };
26
- const region = zone.slice(0, slash);
27
- return { value: zone, label: cityOf(zone.slice(slash + 1)), group: regionLabel(region) ?? region };
28
- });
29
- return [...rows.filter((row) => !row.group), ...rows.filter((row) => row.group)];
30
- }
31
- function regionName(regions, region) {
32
- return Object.prototype.hasOwnProperty.call(regions, region) ? regions[region] : void 0;
33
- }
34
- function zoneName(zone, regions) {
35
- const slash = zone.indexOf("/");
36
- if (slash < 0) return zone;
37
- const region = zone.slice(0, slash);
38
- return `${regionName(regions, region) ?? region} / ${cityOf(zone.slice(slash + 1))}`;
39
- }
40
- function TimeZoneSelect({
41
- value,
42
- onChange,
43
- label,
44
- disabled,
45
- id,
46
- className,
47
- commit,
48
- labels: labelsProp
49
- }) {
50
- const labels = useNotificationsLabels(labelsProp);
51
- const { regions } = labels;
52
- const options = useMemo(() => timeZoneOptions(value, (region) => regionName(regions, region)), [value, regions]);
53
- return /* @__PURE__ */ jsx(
54
- InlineEntityCombobox,
55
- {
56
- id,
57
- label: label ?? labels.timeZone,
58
- value,
59
- options,
60
- disabled,
61
- commit,
62
- onChange: (zone) => {
63
- if (zone && zone !== value) onChange(zone);
64
- },
65
- className
66
- }
67
- );
68
- }
69
- export {
70
- TimeZoneSelect,
71
- deviceTimeZone,
72
- regionName,
73
- timeZoneOptions,
74
- zoneName
75
- };
76
- //# sourceMappingURL=time-zone-select.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/notifications/time-zone-select.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\n\nimport type { LabelOverride } from \"../i18n/kit-labels\";\nimport type { ComboOption } from \"../components/combobox-core\";\nimport { InlineEntityCombobox } from \"../components/inline-entity-combobox\";\nimport type { CommitScope } from \"../components/write-lock\";\nimport { useNotificationsLabels } from \"./notification-parts\";\nimport type { NotificationRegionLabels, NotificationsLabels } from \"./notifications-labels\";\n\n/**\n * This device's IANA zone (\"Europe/Zurich\"), or `\"UTC\"` where the runtime names none —\n * the zone `useDeviceZoneReport` reports, which becomes the account's (§10.3, automatic\n * only).\n */\nexport function deviceTimeZone(): string {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || \"UTC\";\n } catch {\n return \"UTC\";\n }\n}\n\n/** Every zone the runtime lists; `[]` where `Intl.supportedValuesOf` is missing. */\nfunction runtimeZones(): string[] {\n const intl = Intl as typeof Intl & { supportedValuesOf?: (key: \"timeZone\") => string[] };\n return typeof intl.supportedValuesOf === \"function\" ? intl.supportedValuesOf(\"timeZone\") : [];\n}\n\n/** \"Argentina/Buenos_Aires\" → \"Argentina / Buenos Aires\": the zone's city, as a row. */\nfunction cityOf(rest: string): string {\n return rest.replace(/_/g, \" \").replace(/\\//g, \" / \");\n}\n\n/**\n * The zone picker's rows (keksdose `notifications-card.tsx:131-146`): every zone `Intl`\n * lists, as its city (\"Argentina / Buenos Aires\") under its region, the region named by\n * `regionLabel` — which answers `undefined` for a region it does not know, and the region\n * keeps its IANA name. A zone with no region (\"UTC\") comes first, ahead of every heading,\n * rather than trailing the last group as if it were Pacific. `current` is kept even when\n * the runtime does not list it (an old alias such as \"Europe/Kiev\"): a picker without its\n * own value would show an empty field.\n * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.\n */\nexport function timeZoneOptions(\n current: string,\n regionLabel: (region: string) => string | undefined,\n): ComboOption<string>[] {\n const zones = runtimeZones();\n const all = !current || zones.includes(current) ? zones : [...zones, current].sort();\n const rows = all.map((zone): ComboOption<string> => {\n const slash = zone.indexOf(\"/\");\n if (slash < 0) return { value: zone, label: zone };\n const region = zone.slice(0, slash);\n return { value: zone, label: cityOf(zone.slice(slash + 1)), group: regionLabel(region) ?? region };\n });\n return [...rows.filter((row) => !row.group), ...rows.filter((row) => row.group)];\n}\n\n/** The region's name in `regions`, or `undefined` for one the labels do not know.\n * @internal Not part of the barrel. */\nexport function regionName(regions: NotificationRegionLabels, region: string): string | undefined {\n return Object.prototype.hasOwnProperty.call(regions, region)\n ? regions[region as keyof NotificationRegionLabels]\n : undefined;\n}\n\n/** A zone as the picker shows it, for a sentence: \"Europe / Zurich\", the region in the\n * kit's words. @internal Not part of the barrel. */\nexport function zoneName(zone: string, regions: NotificationRegionLabels): string {\n const slash = zone.indexOf(\"/\");\n if (slash < 0) return zone;\n const region = zone.slice(0, slash);\n return `${regionName(regions, region) ?? region} / ${cityOf(zone.slice(slash + 1))}`;\n}\n\n/** @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47. */\nexport interface TimeZoneSelectProps {\n /** The IANA zone. Not clearable: a zone can be changed, never emptied. */\n value: string;\n /** The zone picked. Never called for the value it already has. */\n onChange: (zone: string) => void;\n /** Default `labels.timeZone`. */\n label?: ReactNode;\n disabled?: boolean;\n /** The input's id. */\n id?: string;\n className?: string;\n /** The picker SAVES on change (quiet hours do): locked under a write lock in scope. */\n commit?: CommitScope;\n labels?: LabelOverride<NotificationsLabels>;\n}\n\n/**\n * An IANA time zone, searchable (docs/notifications-harmonization.md §4.2, keksdose's\n * quiet-hours picker): type \"Zur\" for Europe/Zurich, and on a phone the kit's sheet with\n * a search box instead of a 400-row wheel. Rows are the city under its region, the region\n * in the kit's words (`labels.regions`); a zone with no region first; a stored zone the\n * runtime does not list is kept. On the kit's {@link InlineEntityCombobox}.\n * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.\n */\nexport function TimeZoneSelect({\n value,\n onChange,\n label,\n disabled,\n id,\n className,\n commit,\n labels: labelsProp,\n}: TimeZoneSelectProps) {\n const labels = useNotificationsLabels(labelsProp);\n const { regions } = labels;\n const options = useMemo(() => timeZoneOptions(value, (region) => regionName(regions, region)), [value, regions]);\n return (\n <InlineEntityCombobox<string>\n id={id}\n label={label ?? labels.timeZone}\n value={value}\n options={options}\n disabled={disabled}\n commit={commit}\n onChange={(zone) => {\n if (zone && zone !== value) onChange(zone);\n }}\n className={className}\n />\n );\n}\n"],"mappings":";AAmHI;AAnHJ,SAAS,eAAe;AAKxB,SAAS,4BAA4B;AAErC,SAAS,8BAA8B;AAQhC,SAAS,iBAAyB;AACvC,MAAI;AACF,WAAO,KAAK,eAAe,EAAE,gBAAgB,EAAE,YAAY;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,eAAyB;AAChC,QAAM,OAAO;AACb,SAAO,OAAO,KAAK,sBAAsB,aAAa,KAAK,kBAAkB,UAAU,IAAI,CAAC;AAC9F;AAGA,SAAS,OAAO,MAAsB;AACpC,SAAO,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,OAAO,KAAK;AACrD;AAYO,SAAS,gBACd,SACA,aACuB;AACvB,QAAM,QAAQ,aAAa;AAC3B,QAAM,MAAM,CAAC,WAAW,MAAM,SAAS,OAAO,IAAI,QAAQ,CAAC,GAAG,OAAO,OAAO,EAAE,KAAK;AACnF,QAAM,OAAO,IAAI,IAAI,CAAC,SAA8B;AAClD,UAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,QAAI,QAAQ,EAAG,QAAO,EAAE,OAAO,MAAM,OAAO,KAAK;AACjD,UAAM,SAAS,KAAK,MAAM,GAAG,KAAK;AAClC,WAAO,EAAE,OAAO,MAAM,OAAO,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,GAAG,OAAO,YAAY,MAAM,KAAK,OAAO;AAAA,EACnG,CAAC;AACD,SAAO,CAAC,GAAG,KAAK,OAAO,CAAC,QAAQ,CAAC,IAAI,KAAK,GAAG,GAAG,KAAK,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC;AACjF;AAIO,SAAS,WAAW,SAAmC,QAAoC;AAChG,SAAO,OAAO,UAAU,eAAe,KAAK,SAAS,MAAM,IACvD,QAAQ,MAAwC,IAChD;AACN;AAIO,SAAS,SAAS,MAAc,SAA2C;AAChF,QAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,MAAI,QAAQ,EAAG,QAAO;AACtB,QAAM,SAAS,KAAK,MAAM,GAAG,KAAK;AAClC,SAAO,GAAG,WAAW,SAAS,MAAM,KAAK,MAAM,MAAM,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC,CAAC;AACpF;AA2BO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AACV,GAAwB;AACtB,QAAM,SAAS,uBAAuB,UAAU;AAChD,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,UAAU,QAAQ,MAAM,gBAAgB,OAAO,CAAC,WAAW,WAAW,SAAS,MAAM,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC;AAC/G,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,OAAO,SAAS,OAAO;AAAA,MACvB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,CAAC,SAAS;AAClB,YAAI,QAAQ,SAAS,MAAO,UAAS,IAAI;AAAA,MAC3C;AAAA,MACA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -1,129 +0,0 @@
1
- import { useMemo } from "react";
2
- import type { ReactNode } from "react";
3
-
4
- import type { LabelOverride } from "../i18n/kit-labels";
5
- import type { ComboOption } from "../components/combobox-core";
6
- import { InlineEntityCombobox } from "../components/inline-entity-combobox";
7
- import type { CommitScope } from "../components/write-lock";
8
- import { useNotificationsLabels } from "./notification-parts";
9
- import type { NotificationRegionLabels, NotificationsLabels } from "./notifications-labels";
10
-
11
- /**
12
- * This device's IANA zone ("Europe/Zurich"), or `"UTC"` where the runtime names none —
13
- * the zone `useDeviceZoneReport` reports, which becomes the account's (§10.3, automatic
14
- * only).
15
- */
16
- export function deviceTimeZone(): string {
17
- try {
18
- return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
19
- } catch {
20
- return "UTC";
21
- }
22
- }
23
-
24
- /** Every zone the runtime lists; `[]` where `Intl.supportedValuesOf` is missing. */
25
- function runtimeZones(): string[] {
26
- const intl = Intl as typeof Intl & { supportedValuesOf?: (key: "timeZone") => string[] };
27
- return typeof intl.supportedValuesOf === "function" ? intl.supportedValuesOf("timeZone") : [];
28
- }
29
-
30
- /** "Argentina/Buenos_Aires" → "Argentina / Buenos Aires": the zone's city, as a row. */
31
- function cityOf(rest: string): string {
32
- return rest.replace(/_/g, " ").replace(/\//g, " / ");
33
- }
34
-
35
- /**
36
- * The zone picker's rows (keksdose `notifications-card.tsx:131-146`): every zone `Intl`
37
- * lists, as its city ("Argentina / Buenos Aires") under its region, the region named by
38
- * `regionLabel` — which answers `undefined` for a region it does not know, and the region
39
- * keeps its IANA name. A zone with no region ("UTC") comes first, ahead of every heading,
40
- * rather than trailing the last group as if it were Pacific. `current` is kept even when
41
- * the runtime does not list it (an old alias such as "Europe/Kiev"): a picker without its
42
- * own value would show an empty field.
43
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
44
- */
45
- export function timeZoneOptions(
46
- current: string,
47
- regionLabel: (region: string) => string | undefined,
48
- ): ComboOption<string>[] {
49
- const zones = runtimeZones();
50
- const all = !current || zones.includes(current) ? zones : [...zones, current].sort();
51
- const rows = all.map((zone): ComboOption<string> => {
52
- const slash = zone.indexOf("/");
53
- if (slash < 0) return { value: zone, label: zone };
54
- const region = zone.slice(0, slash);
55
- return { value: zone, label: cityOf(zone.slice(slash + 1)), group: regionLabel(region) ?? region };
56
- });
57
- return [...rows.filter((row) => !row.group), ...rows.filter((row) => row.group)];
58
- }
59
-
60
- /** The region's name in `regions`, or `undefined` for one the labels do not know.
61
- * @internal Not part of the barrel. */
62
- export function regionName(regions: NotificationRegionLabels, region: string): string | undefined {
63
- return Object.prototype.hasOwnProperty.call(regions, region)
64
- ? regions[region as keyof NotificationRegionLabels]
65
- : undefined;
66
- }
67
-
68
- /** A zone as the picker shows it, for a sentence: "Europe / Zurich", the region in the
69
- * kit's words. @internal Not part of the barrel. */
70
- export function zoneName(zone: string, regions: NotificationRegionLabels): string {
71
- const slash = zone.indexOf("/");
72
- if (slash < 0) return zone;
73
- const region = zone.slice(0, slash);
74
- return `${regionName(regions, region) ?? region} / ${cityOf(zone.slice(slash + 1))}`;
75
- }
76
-
77
- /** @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47. */
78
- export interface TimeZoneSelectProps {
79
- /** The IANA zone. Not clearable: a zone can be changed, never emptied. */
80
- value: string;
81
- /** The zone picked. Never called for the value it already has. */
82
- onChange: (zone: string) => void;
83
- /** Default `labels.timeZone`. */
84
- label?: ReactNode;
85
- disabled?: boolean;
86
- /** The input's id. */
87
- id?: string;
88
- className?: string;
89
- /** The picker SAVES on change (quiet hours do): locked under a write lock in scope. */
90
- commit?: CommitScope;
91
- labels?: LabelOverride<NotificationsLabels>;
92
- }
93
-
94
- /**
95
- * An IANA time zone, searchable (docs/notifications-harmonization.md §4.2, keksdose's
96
- * quiet-hours picker): type "Zur" for Europe/Zurich, and on a phone the kit's sheet with
97
- * a search box instead of a 400-row wheel. Rows are the city under its region, the region
98
- * in the kit's words (`labels.regions`); a zone with no region first; a stored zone the
99
- * runtime does not list is kept. On the kit's {@link InlineEntityCombobox}.
100
- * @deprecated 0.46 — the zone is automatic (§10.3, amended 2026-10-20), no app picks one; removed in 0.47.
101
- */
102
- export function TimeZoneSelect({
103
- value,
104
- onChange,
105
- label,
106
- disabled,
107
- id,
108
- className,
109
- commit,
110
- labels: labelsProp,
111
- }: TimeZoneSelectProps) {
112
- const labels = useNotificationsLabels(labelsProp);
113
- const { regions } = labels;
114
- const options = useMemo(() => timeZoneOptions(value, (region) => regionName(regions, region)), [value, regions]);
115
- return (
116
- <InlineEntityCombobox<string>
117
- id={id}
118
- label={label ?? labels.timeZone}
119
- value={value}
120
- options={options}
121
- disabled={disabled}
122
- commit={commit}
123
- onChange={(zone) => {
124
- if (zone && zone !== value) onChange(zone);
125
- }}
126
- className={className}
127
- />
128
- );
129
- }