@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.
- package/README.md +12 -11
- package/dist/admin/user-roster.d.ts +6 -2
- package/dist/admin/user-roster.js +7 -3
- package/dist/admin/user-roster.js.map +1 -1
- package/dist/components/data-table-row-actions.d.ts +22 -5
- package/dist/components/data-table-row-actions.js +14 -5
- package/dist/components/data-table-row-actions.js.map +1 -1
- package/dist/components/data-table.d.ts +16 -1
- package/dist/components/data-table.js +20 -1
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/row-actions.d.ts +37 -5
- package/dist/components/row-actions.js +22 -5
- package/dist/components/row-actions.js.map +1 -1
- package/dist/components/tabs.d.ts +24 -16
- package/dist/components/tabs.js +37 -31
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/translation-review.js +3 -1
- package/dist/components/translation-review.js.map +1 -1
- package/dist/components/ui-labels.js +1 -1
- package/dist/components/ui-labels.js.map +1 -1
- package/dist/data-table.d.ts +1 -0
- package/dist/data-table.js +2 -0
- package/dist/data-table.js.map +1 -1
- package/dist/eslint/index.js +1 -1
- package/dist/feedback/feedback-attachment-download.d.ts +19 -0
- package/dist/feedback/feedback-attachment-download.js +59 -0
- package/dist/feedback/feedback-attachment-download.js.map +1 -0
- package/dist/feedback/feedback-comment-thread.d.ts +17 -6
- package/dist/feedback/feedback-comment-thread.js +21 -2
- package/dist/feedback/feedback-comment-thread.js.map +1 -1
- package/dist/feedback/feedback-entry-file.d.ts +61 -0
- package/dist/feedback/feedback-entry-file.js +76 -0
- package/dist/feedback/feedback-entry-file.js.map +1 -0
- package/dist/feedback/feedback-public-list.d.ts +18 -5
- package/dist/feedback/feedback-public-list.js +8 -0
- package/dist/feedback/feedback-public-list.js.map +1 -1
- package/dist/feedback/feedback-public-types.d.ts +7 -2
- package/dist/feedback/feedback-replies.d.ts +26 -9
- package/dist/feedback/feedback-replies.js +20 -10
- package/dist/feedback/feedback-replies.js.map +1 -1
- package/dist/feedback/feedback-row-detail-labels.js +0 -1
- package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
- package/dist/feedback/feedback-row-detail.d.ts +23 -10
- package/dist/feedback/feedback-row-detail.js +10 -54
- package/dist/feedback/feedback-row-detail.js.map +1 -1
- package/dist/feedback.d.ts +1 -0
- package/dist/feedback.js +1 -0
- package/dist/feedback.js.map +1 -1
- package/dist/i18n/german.js +1 -6
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/locales/en.js +1 -6
- package/dist/i18n/locales/en.js.map +2 -2
- package/dist/i18n/locales/es.js +1 -6
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.js +1 -6
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.js +1 -6
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.js +1 -6
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.js +1 -6
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.js +0 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +5 -7
- package/dist/index.js +14 -5
- package/dist/index.js.map +1 -1
- package/dist/notifications/device-zone-report.d.ts +7 -0
- package/dist/notifications/device-zone-report.js +8 -1
- package/dist/notifications/device-zone-report.js.map +1 -1
- package/dist/notifications/feedback-notification-types.d.ts +14 -1
- package/dist/notifications/feedback-notification-types.js +5 -1
- package/dist/notifications/feedback-notification-types.js.map +1 -1
- package/dist/notifications/notification-bell.d.ts +1 -1
- package/dist/notifications/notification-bell.js.map +1 -1
- package/dist/notifications/notification-inbox.d.ts +12 -3
- package/dist/notifications/notification-inbox.js +2 -2
- package/dist/notifications/notification-inbox.js.map +1 -1
- package/dist/notifications/notification-types.d.ts +0 -3
- package/dist/notifications/notifications-labels.d.ts +2 -13
- package/dist/notifications/notifications-labels.js +0 -4
- package/dist/notifications/notifications-labels.js.map +1 -1
- package/dist/notifications/quiet-hours-setting.d.ts +3 -8
- package/dist/notifications/quiet-hours-setting.js +8 -3
- package/dist/notifications/quiet-hours-setting.js.map +1 -1
- package/dist/offline/local-first.d.ts +9 -0
- package/dist/offline/local-first.js +14 -0
- package/dist/offline/local-first.js.map +1 -1
- package/dist/offline.d.ts +1 -1
- package/dist/offline.js +2 -1
- package/dist/offline.js.map +1 -1
- package/dist/sync/db.d.ts +17 -0
- package/dist/sync/db.js +10 -1
- package/dist/sync/db.js.map +1 -1
- package/dist/sync/engine.d.ts +11 -9
- package/dist/sync/engine.js.map +1 -1
- package/dist/sync/hooks.d.ts +5 -0
- package/dist/sync/hooks.js.map +1 -1
- package/dist/sync.d.ts +2 -2
- package/dist/sync.js +5 -2
- package/dist/sync.js.map +1 -1
- package/dist/theme/theme-store.d.ts +4 -4
- package/dist/theme/theme-store.js +1 -0
- package/dist/theme/theme-store.js.map +1 -1
- package/package.json +2 -2
- package/src/admin/user-roster.tsx +12 -3
- package/src/components/data-table-row-actions.tsx +33 -9
- package/src/components/data-table.tsx +46 -3
- package/src/components/row-actions.tsx +73 -10
- package/src/components/tabs.tsx +66 -44
- package/src/components/translation-review.tsx +3 -1
- package/src/components/ui-labels.ts +1 -1
- package/src/data-table.ts +3 -0
- package/src/feedback/feedback-attachment-download.tsx +77 -0
- package/src/feedback/feedback-comment-thread.tsx +42 -7
- package/src/feedback/feedback-entry-file.tsx +150 -0
- package/src/feedback/feedback-public-list.tsx +25 -4
- package/src/feedback/feedback-public-types.ts +7 -2
- package/src/feedback/feedback-replies.tsx +46 -19
- package/src/feedback/feedback-row-detail-labels.ts +0 -1
- package/src/feedback/feedback-row-detail.tsx +35 -86
- package/src/feedback.ts +1 -0
- package/src/i18n/german.ts +1 -6
- package/src/i18n/locales/es.ts +1 -6
- package/src/i18n/locales/fr.ts +1 -6
- package/src/i18n/locales/hu.ts +1 -6
- package/src/i18n/locales/it.ts +1 -6
- package/src/i18n/locales/zh.ts +1 -6
- package/src/i18n/review.ts +0 -1
- package/src/index.ts +19 -5
- package/src/notifications/device-zone-report.ts +14 -1
- package/src/notifications/feedback-notification-types.ts +19 -1
- package/src/notifications/notification-bell.tsx +1 -1
- package/src/notifications/notification-inbox.tsx +15 -5
- package/src/notifications/notification-types.ts +0 -3
- package/src/notifications/notifications-labels.ts +2 -17
- package/src/notifications/quiet-hours-setting.tsx +16 -11
- package/src/offline/local-first.ts +24 -0
- package/src/offline.ts +1 -1
- package/src/sync/db.ts +22 -0
- package/src/sync/engine.ts +11 -9
- package/src/sync/hooks.ts +5 -0
- package/src/sync.ts +2 -2
- package/src/theme/theme-store.ts +13 -5
- package/dist/notifications/time-zone-select.d.ts +0 -53
- package/dist/notifications/time-zone-select.js +0 -76
- package/dist/notifications/time-zone-select.js.map +0 -1
- package/src/notifications/time-zone-select.tsx +0 -129
package/src/sync/engine.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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";
|
package/src/theme/theme-store.ts
CHANGED
|
@@ -47,20 +47,28 @@ function preferenceFromState(
|
|
|
47
47
|
return "system";
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
/**
|
|
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,
|
|
61
|
-
* already has the right theme (no flash). Returns the resolved
|
|
62
|
-
* can chain {@link applyPersistedPalette}. Encapsulates the
|
|
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
|
-
}
|