@gooddata/sdk-ui-kit 11.51.0-alpha.0 → 11.51.0-alpha.2
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/esm/@ui/UiGeneralAccessRadio/UiGeneralAccessRadio.d.ts +3 -2
- package/esm/@ui/UiGeneralAccessRadio/UiGeneralAccessRadio.d.ts.map +1 -1
- package/esm/@ui/UiGeneralAccessRadio/UiGeneralAccessRadio.js +7 -3
- package/esm/@ui/UiGranteeAsyncPicker/UiGranteeAsyncPicker.d.ts +5 -0
- package/esm/@ui/UiGranteeAsyncPicker/UiGranteeAsyncPicker.d.ts.map +1 -1
- package/esm/@ui/UiGranteeAsyncPicker/UiGranteeAsyncPicker.js +3 -5
- package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.d.ts +10 -16
- package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.d.ts.map +1 -1
- package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.js +19 -32
- package/esm/@ui/UiLabelsChecklist/UiLabelsChecklist.d.ts +3 -1
- package/esm/@ui/UiLabelsChecklist/UiLabelsChecklist.d.ts.map +1 -1
- package/esm/@ui/UiLabelsChecklist/UiLabelsChecklist.js +12 -3
- package/esm/@ui/UiObjectShareDialog/UiObjectShareDialogCard.d.ts +9 -3
- package/esm/@ui/UiObjectShareDialog/UiObjectShareDialogCard.d.ts.map +1 -1
- package/esm/@ui/UiObjectShareDialog/UiObjectShareDialogCard.js +3 -3
- package/esm/@ui/UiPermissionMenu/UiPermissionMenu.d.ts +18 -10
- package/esm/@ui/UiPermissionMenu/UiPermissionMenu.d.ts.map +1 -1
- package/esm/@ui/UiPermissionMenu/UiPermissionMenu.js +45 -13
- package/esm/@ui/UiSubmenuHeader/UiSubmenuHeader.d.ts +4 -2
- package/esm/@ui/UiSubmenuHeader/UiSubmenuHeader.d.ts.map +1 -1
- package/esm/@ui/UiSubmenuHeader/UiSubmenuHeader.js +2 -2
- package/esm/@ui/hooks/useCloseOnEscape.d.ts +4 -1
- package/esm/@ui/hooks/useCloseOnEscape.d.ts.map +1 -1
- package/esm/@ui/hooks/useCloseOnEscape.js +11 -5
- package/esm/List/InvertableSelect/InvertableSelectVirtualised.d.ts.map +1 -1
- package/esm/List/InvertableSelect/InvertableSelectVirtualised.js +4 -1
- package/esm/ParameterControl/StringParameterControlDropdown.js +2 -2
- package/esm/Timezone/TimezoneSelect.d.ts +108 -0
- package/esm/Timezone/TimezoneSelect.d.ts.map +1 -0
- package/esm/Timezone/TimezoneSelect.js +90 -0
- package/esm/Timezone/timezones.d.ts +86 -0
- package/esm/Timezone/timezones.d.ts.map +1 -0
- package/esm/Timezone/timezones.js +253 -0
- package/esm/index.d.ts +3 -2
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +3 -2
- package/esm/locales.d.ts +9 -6
- package/esm/locales.d.ts.map +1 -1
- package/esm/locales.js +4 -3
- package/esm/sdk-ui-kit.d.ts +242 -62
- package/package.json +11 -11
- package/src/@ui/UiGranteeRowControls/UiGranteeRowControls.scss +0 -8
- package/src/@ui/UiLabelsChecklist/UiLabelsChecklist.scss +2 -2
- package/src/@ui/UiPermissionMenu/UiPermissionMenu.scss +5 -0
- package/src/@ui/index.scss +0 -1
- package/src/Timezone/TimezoneSelect.scss +22 -0
- package/styles/css/button.css.map +1 -1
- package/styles/css/header.css +3 -3
- package/styles/css/header.css.map +1 -1
- package/styles/css/main.css +192 -187
- package/styles/css/main.css.map +1 -1
- package/styles/scss/Button/_mixins.scss +5 -157
- package/styles/scss/Button/_placeholders.scss +169 -0
- package/styles/scss/button.scss +1 -0
- package/styles/scss/header.scss +8 -3
- package/styles/scss/main.scss +1 -0
- package/esm/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.d.ts +0 -28
- package/esm/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.d.ts.map +0 -1
- package/esm/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.js +0 -85
- package/src/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.scss +0 -10
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hook to trigger a callback when pressing the Escape key.
|
|
3
3
|
*
|
|
4
|
+
* @param capture - When true, listens in the capture phase and stops propagation, so a nested
|
|
5
|
+
* overlay can dismiss on Escape without an ancestor (e.g. a Dropdown) also closing.
|
|
6
|
+
*
|
|
4
7
|
* @internal
|
|
5
8
|
*/
|
|
6
|
-
export declare function useCloseOnEscape(isOpen: boolean, onClose: () => void): void;
|
|
9
|
+
export declare function useCloseOnEscape(isOpen: boolean, onClose: () => void, capture?: boolean): void;
|
|
7
10
|
//# sourceMappingURL=useCloseOnEscape.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCloseOnEscape.d.ts","sourceRoot":"","sources":["../../../src/@ui/hooks/useCloseOnEscape.ts"],"names":[],"mappings":"AAIA
|
|
1
|
+
{"version":3,"file":"useCloseOnEscape.d.ts","sourceRoot":"","sources":["../../../src/@ui/hooks/useCloseOnEscape.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,EAAE,OAAO,UAAQ,GAAG,IAAI,CAkB5F"}
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
-
// (C) 2025 GoodData Corporation
|
|
1
|
+
// (C) 2025-2026 GoodData Corporation
|
|
2
2
|
import { useEffect } from "react";
|
|
3
3
|
/**
|
|
4
4
|
* Hook to trigger a callback when pressing the Escape key.
|
|
5
5
|
*
|
|
6
|
+
* @param capture - When true, listens in the capture phase and stops propagation, so a nested
|
|
7
|
+
* overlay can dismiss on Escape without an ancestor (e.g. a Dropdown) also closing.
|
|
8
|
+
*
|
|
6
9
|
* @internal
|
|
7
10
|
*/
|
|
8
|
-
export function useCloseOnEscape(isOpen, onClose) {
|
|
11
|
+
export function useCloseOnEscape(isOpen, onClose, capture = false) {
|
|
9
12
|
useEffect(() => {
|
|
10
13
|
if (!isOpen) {
|
|
11
14
|
return undefined;
|
|
12
15
|
}
|
|
13
16
|
const handleKeyDown = (event) => {
|
|
14
17
|
if (event.key === "Escape") {
|
|
18
|
+
if (capture) {
|
|
19
|
+
event.stopPropagation();
|
|
20
|
+
}
|
|
15
21
|
onClose();
|
|
16
22
|
}
|
|
17
23
|
};
|
|
18
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
19
|
-
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
20
|
-
}, [isOpen, onClose]);
|
|
24
|
+
document.addEventListener("keydown", handleKeyDown, { capture });
|
|
25
|
+
return () => document.removeEventListener("keydown", handleKeyDown, { capture });
|
|
26
|
+
}, [isOpen, onClose, capture]);
|
|
21
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvertableSelectVirtualised.d.ts","sourceRoot":"","sources":["../../../src/List/InvertableSelect/InvertableSelectVirtualised.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"InvertableSelectVirtualised.d.ts","sourceRoot":"","sources":["../../../src/List/InvertableSelect/InvertableSelectVirtualised.tsx"],"names":[],"mappings":"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"}
|
|
@@ -133,7 +133,9 @@ export function InvertableSelectVirtualised(props) {
|
|
|
133
133
|
item,
|
|
134
134
|
title: getItemTitle(item),
|
|
135
135
|
isSelected: getIsItemSelected(item),
|
|
136
|
-
focusedAction
|
|
136
|
+
// `focusedAction` persists across blur (so Tab-back-in resumes sensibly), so gate
|
|
137
|
+
// it on `isFocusWithin` here to avoid reporting a stale focus to consumers.
|
|
138
|
+
focusedAction: isFocusWithin && item === focusedItem ? focusedAction : undefined,
|
|
137
139
|
listRef: containerRef,
|
|
138
140
|
index: items.indexOf(item) + 1,
|
|
139
141
|
itemsCount: items.length,
|
|
@@ -144,6 +146,7 @@ export function InvertableSelectVirtualised(props) {
|
|
|
144
146
|
getIsItemSelected,
|
|
145
147
|
focusedItem,
|
|
146
148
|
focusedAction,
|
|
149
|
+
isFocusWithin,
|
|
147
150
|
containerRef,
|
|
148
151
|
items,
|
|
149
152
|
selectItems,
|
|
@@ -22,10 +22,10 @@ export function StringParameterControlDropdown({ name, value, resetValue, constr
|
|
|
22
22
|
return (_jsx(ParameterControlDropdown, { name: name, draft: draft, onDraftChange: setDraft, inputType: "text", inputId: inputId, ariaAttributes: ariaAttributes, errorMessage: error ? (_jsx(FormattedMessage, { ...error, values: { minLength: constraints?.minLength, maxLength: constraints?.maxLength } })) : undefined, onReset: showReset ? () => setDraft(resetValue) : undefined, onApply: () => onApply(draft), onCancel: onCancel }));
|
|
23
23
|
}
|
|
24
24
|
function getStringDraftValidationError(draft, constraints) {
|
|
25
|
-
|
|
25
|
+
const { minLength, maxLength } = constraints ?? {};
|
|
26
|
+
if (isValidStringParameterValue(draft, { minLength, maxLength })) {
|
|
26
27
|
return undefined;
|
|
27
28
|
}
|
|
28
|
-
const { minLength } = constraints ?? {};
|
|
29
29
|
return minLength !== undefined && draft.length < minLength
|
|
30
30
|
? messages.errorTooShort
|
|
31
31
|
: messages.errorTooLong;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type ReactElement, type ReactNode } from "react";
|
|
2
|
+
import { type IUiListboxItem } from "../@ui/UiListbox/types.js";
|
|
3
|
+
import { type IDropdownButtonRenderProps } from "../Dropdown/Dropdown.js";
|
|
4
|
+
/**
|
|
5
|
+
* Special item rendered above the divider in {@link TimezoneSelect}, ahead of the curated
|
|
6
|
+
* timezone list. Callers use these for entries like "Default (workspace)" or "From browser (...)".
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export interface ITimezoneSelectSpecialItem {
|
|
11
|
+
/**
|
|
12
|
+
* Value emitted by onChange when this item is selected. Undefined typically represents
|
|
13
|
+
* "no explicit timezone" (e.g. inherit the workspace setting).
|
|
14
|
+
*/
|
|
15
|
+
id: string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Display label of the item.
|
|
18
|
+
*/
|
|
19
|
+
label: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Render props passed to a custom trigger renderer of {@link TimezoneSelect}.
|
|
23
|
+
*
|
|
24
|
+
* Extends the {@link IDropdownButtonRenderProps} contract of the underlying dropdown
|
|
25
|
+
* (toggle/open/close callbacks, isOpen, buttonRef and accessibility props) with the
|
|
26
|
+
* current selection so that custom triggers can display it.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export interface ITimezoneSelectButtonRenderProps extends IDropdownButtonRenderProps {
|
|
31
|
+
/**
|
|
32
|
+
* Human-readable label of the current selection: the matching special item label,
|
|
33
|
+
* the selected timezone as "name (offset)" (e.g. "Prague (GMT+01:00)"),
|
|
34
|
+
* or the placeholder when nothing is selected.
|
|
35
|
+
*/
|
|
36
|
+
buttonLabel: string;
|
|
37
|
+
/**
|
|
38
|
+
* Currently selected timezone ID (IANA ID, a special item ID or undefined).
|
|
39
|
+
*/
|
|
40
|
+
value: string | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the select is disabled. Custom triggers should reflect this state.
|
|
43
|
+
*/
|
|
44
|
+
isDisabled?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* All label props are required so that callers pass localized strings — the component
|
|
48
|
+
* has no built-in English defaults to leak into the UI.
|
|
49
|
+
*
|
|
50
|
+
* @internal
|
|
51
|
+
*/
|
|
52
|
+
export interface ITimezoneSelectProps {
|
|
53
|
+
/**
|
|
54
|
+
* Currently selected timezone ID (IANA ID, a special item ID or undefined).
|
|
55
|
+
*/
|
|
56
|
+
value?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Called when the user selects a timezone.
|
|
59
|
+
*/
|
|
60
|
+
onChange: (id: string | undefined) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Items rendered above the divider, ahead of the curated timezone list.
|
|
63
|
+
*/
|
|
64
|
+
specialItems?: ITimezoneSelectSpecialItem[];
|
|
65
|
+
/**
|
|
66
|
+
* Label of the dropdown button when nothing is selected and no special item matches.
|
|
67
|
+
*/
|
|
68
|
+
placeholder?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Placeholder of the search input.
|
|
71
|
+
*/
|
|
72
|
+
searchPlaceholder: string;
|
|
73
|
+
/**
|
|
74
|
+
* Accessibility label of the search input and the listbox.
|
|
75
|
+
*/
|
|
76
|
+
ariaLabel: string;
|
|
77
|
+
/**
|
|
78
|
+
* Label shown when the search yields no results.
|
|
79
|
+
*/
|
|
80
|
+
noMatchLabel: string;
|
|
81
|
+
isDisabled?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Optional content rendered above the search input (e.g. a dialog-like header).
|
|
84
|
+
*/
|
|
85
|
+
header?: ReactNode;
|
|
86
|
+
/**
|
|
87
|
+
* Custom renderer of the dropdown trigger. When not provided, a standard dropdown
|
|
88
|
+
* button showing the current selection is rendered.
|
|
89
|
+
*
|
|
90
|
+
* Use this to embed the select in surfaces where the default button does not fit,
|
|
91
|
+
* e.g. as an item of an options menu. The renderer receives the dropdown render props
|
|
92
|
+
* ({@link IDropdownButtonRenderProps}: toggleDropdown, isOpen, buttonRef, accessibility
|
|
93
|
+
* props, ...) together with the current selection ({@link ITimezoneSelectButtonRenderProps}).
|
|
94
|
+
*/
|
|
95
|
+
renderButton?: (props: ITimezoneSelectButtonRenderProps) => ReactNode;
|
|
96
|
+
}
|
|
97
|
+
type TimezoneListItemData = string | undefined;
|
|
98
|
+
export declare function buildListboxItems(specialItems: ITimezoneSelectSpecialItem[], searchString: string): IUiListboxItem<TimezoneListItemData>[];
|
|
99
|
+
export declare function getSelectedItemId(value: string | undefined, specialItems: ITimezoneSelectSpecialItem[]): string | undefined;
|
|
100
|
+
/**
|
|
101
|
+
* Searchable dropdown for picking a timezone from the curated list, with optional special
|
|
102
|
+
* items (e.g. "Default (workspace)", "From browser (...)") rendered above a divider.
|
|
103
|
+
*
|
|
104
|
+
* @internal
|
|
105
|
+
*/
|
|
106
|
+
export declare function TimezoneSelect({ value, onChange, specialItems, placeholder, searchPlaceholder, ariaLabel, noMatchLabel, isDisabled, header, renderButton }: ITimezoneSelectProps): ReactElement;
|
|
107
|
+
export {};
|
|
108
|
+
//# sourceMappingURL=TimezoneSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimezoneSelect.d.ts","sourceRoot":"","sources":["../../src/Timezone/TimezoneSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,SAAS,EAAqB,MAAM,OAAO,CAAC;AAG7E,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhG,OAAO,EAAY,KAAK,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAYpF;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACvC;;;OAGG;IACH,EAAE,EAAE,MAAM,GAAG,SAAS,CAAC;IAEvB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gCAAiC,SAAQ,0BAA0B;IAChF;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAE1B;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACjC;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAE3C;;OAEG;IACH,YAAY,CAAC,EAAE,0BAA0B,EAAE,CAAC;IAE5C;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,iBAAiB,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IAEnB;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,SAAS,CAAC;CACzE;AAED,KAAK,oBAAoB,GAAG,MAAM,GAAG,SAAS,CAAC;AAM/C,wBAAgB,iBAAiB,CAC7B,YAAY,EAAE,0BAA0B,EAAE,EAC1C,YAAY,EAAE,MAAM,GACrB,cAAc,CAAC,oBAAoB,CAAC,EAAE,CA6BxC;AAGD,wBAAgB,iBAAiB,CAC7B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,YAAY,EAAE,0BAA0B,EAAE,GAC3C,MAAM,GAAG,SAAS,CAMpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,YAAiB,EACjB,WAAgB,EAChB,iBAAiB,EACjB,SAAS,EACT,YAAY,EACZ,UAAU,EACV,MAAM,EACN,YAAY,EACf,EAAE,oBAAoB,GAAG,YAAY,CA2FrC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { UiButton } from "../@ui/UiButton/UiButton.js";
|
|
5
|
+
import { UiListbox } from "../@ui/UiListbox/UiListbox.js";
|
|
6
|
+
import { Dropdown } from "../Dropdown/Dropdown.js";
|
|
7
|
+
import { Input } from "../Form/Input.js";
|
|
8
|
+
import { NoData } from "../NoData/NoData.js";
|
|
9
|
+
import { getTimezoneDisplayLabel, getTimezones, timezoneMatchesSearch } from "./timezones.js";
|
|
10
|
+
const DROPDOWN_ALIGN_POINTS = [{ align: "bl tl" }, { align: "tl bl" }];
|
|
11
|
+
const DROPDOWN_BODY_WIDTH = 300;
|
|
12
|
+
// ~10 rows of 28px compact list items visible before the list scrolls
|
|
13
|
+
const LIST_MAX_HEIGHT = 280;
|
|
14
|
+
// special items may carry an undefined id, so listbox entries need a synthetic unique id
|
|
15
|
+
const SPECIAL_ITEM_ID_PREFIX = "special-item-";
|
|
16
|
+
// exported for tests
|
|
17
|
+
export function buildListboxItems(specialItems, searchString) {
|
|
18
|
+
const search = searchString.trim().toLowerCase();
|
|
19
|
+
const specialListItems = specialItems
|
|
20
|
+
// keep the pre-filter index — getSelectedItemId derives the selected id from the
|
|
21
|
+
// unfiltered array, so filtered-out items must not shift the ids of the remaining ones
|
|
22
|
+
.map((item, index) => ({ item, index }))
|
|
23
|
+
.filter(({ item }) => !search || item.label.toLowerCase().includes(search))
|
|
24
|
+
.map(({ item, index }) => ({
|
|
25
|
+
type: "interactive",
|
|
26
|
+
id: `${SPECIAL_ITEM_ID_PREFIX}${index}`,
|
|
27
|
+
stringTitle: item.label,
|
|
28
|
+
data: item.id,
|
|
29
|
+
}));
|
|
30
|
+
const timezoneListItems = getTimezones()
|
|
31
|
+
.filter((timezone) => timezoneMatchesSearch(timezone, search))
|
|
32
|
+
.map((timezone) => ({
|
|
33
|
+
type: "interactive",
|
|
34
|
+
id: timezone.id,
|
|
35
|
+
stringTitle: getTimezoneDisplayLabel(timezone.id),
|
|
36
|
+
// the label shows the friendly name; surface the IANA ID on hover
|
|
37
|
+
tooltip: timezone.id,
|
|
38
|
+
data: timezone.id,
|
|
39
|
+
}));
|
|
40
|
+
return specialListItems.length > 0 && timezoneListItems.length > 0
|
|
41
|
+
? [...specialListItems, { type: "separator" }, ...timezoneListItems]
|
|
42
|
+
: [...specialListItems, ...timezoneListItems];
|
|
43
|
+
}
|
|
44
|
+
// exported for tests
|
|
45
|
+
export function getSelectedItemId(value, specialItems) {
|
|
46
|
+
const specialIndex = specialItems.findIndex((item) => item.id === value);
|
|
47
|
+
if (specialIndex >= 0) {
|
|
48
|
+
return `${SPECIAL_ITEM_ID_PREFIX}${specialIndex}`;
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Searchable dropdown for picking a timezone from the curated list, with optional special
|
|
54
|
+
* items (e.g. "Default (workspace)", "From browser (...)") rendered above a divider.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export function TimezoneSelect({ value, onChange, specialItems = [], placeholder = "", searchPlaceholder, ariaLabel, noMatchLabel, isDisabled, header, renderButton, }) {
|
|
59
|
+
const [searchString, setSearchString] = useState("");
|
|
60
|
+
const selectedSpecialItem = specialItems.find((item) => item.id === value);
|
|
61
|
+
const buttonLabel = selectedSpecialItem
|
|
62
|
+
? selectedSpecialItem.label
|
|
63
|
+
: value === undefined
|
|
64
|
+
? placeholder
|
|
65
|
+
: getTimezoneDisplayLabel(value);
|
|
66
|
+
const items = useMemo(() => buildListboxItems(specialItems, searchString), [specialItems, searchString]);
|
|
67
|
+
const hasNoMatchingData = items.length === 0;
|
|
68
|
+
return (_jsx(Dropdown, { closeOnParentScroll: true, alignPoints: DROPDOWN_ALIGN_POINTS, className: "gd-timezone-select s-timezone-select", onOpenStateChanged: (isOpen) => {
|
|
69
|
+
if (!isOpen) {
|
|
70
|
+
setSearchString("");
|
|
71
|
+
}
|
|
72
|
+
}, renderButton: (renderProps) => renderButton ? (renderButton({ ...renderProps, buttonLabel, value, isDisabled })) : (_jsx(UiButton, { label: buttonLabel, onClick: renderProps.toggleDropdown, isDisabled: isDisabled, iconAfter: renderProps.isOpen ? "navigateUp" : "navigateDown", size: "small", dataTestId: "s-timezone-select-button", accessibilityConfig: {
|
|
73
|
+
ariaLabel,
|
|
74
|
+
ariaExpanded: renderProps.isOpen,
|
|
75
|
+
} })), renderBody: ({ closeDropdown, ariaAttributes }) => (_jsxs("div", { className: "gd-timezone-select__body s-timezone-select-body", children: [header, _jsx("div", { className: "gd-timezone-select__search s-timezone-select-search", children: _jsx(Input, { value: searchString, onChange: (newValue) => setSearchString(newValue.toString()), placeholder: searchPlaceholder, autofocus: true, clearOnEsc: true, isSearch: true, isSmall: true, onEscKeyPress: (event) => {
|
|
76
|
+
if (searchString) {
|
|
77
|
+
event.stopPropagation();
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
closeDropdown();
|
|
81
|
+
}
|
|
82
|
+
}, accessibilityConfig: {
|
|
83
|
+
ariaLabel,
|
|
84
|
+
} }) }), hasNoMatchingData ? (_jsx(NoData, { hasNoMatchingData: true, notFoundLabel: noMatchLabel, noDataLabel: noMatchLabel })) : (_jsx(UiListbox
|
|
85
|
+
// remount when the filter changes so the focused index cannot point
|
|
86
|
+
// past the end of a shortened result list, which would break keyboard selection
|
|
87
|
+
, { width: DROPDOWN_BODY_WIDTH, maxHeight: LIST_MAX_HEIGHT, items: items, selectedItemId: getSelectedItemId(value, specialItems), onSelect: (item) => {
|
|
88
|
+
onChange(item.data);
|
|
89
|
+
}, onClose: closeDropdown, ariaAttributes: ariaAttributes, isCompact: true, dataTestId: "s-timezone-select-list", itemDataTestId: "s-timezone-select-item" }, searchString))] })), overlayPositionType: "sameAsTarget" }));
|
|
90
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Item describing a single timezone from the curated list.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export interface ITimezoneItem {
|
|
7
|
+
/**
|
|
8
|
+
* IANA timezone ID, e.g. "Europe/Prague".
|
|
9
|
+
*/
|
|
10
|
+
id: string;
|
|
11
|
+
/**
|
|
12
|
+
* Human readable name without the offset, e.g. "Prague" or "Pacific Time (US & Canada)".
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* GMT offset label, e.g. "GMT+01:00".
|
|
17
|
+
*/
|
|
18
|
+
offsetLabel: string;
|
|
19
|
+
/**
|
|
20
|
+
* Offset from UTC in minutes as observed in January (northern hemisphere winter).
|
|
21
|
+
*/
|
|
22
|
+
januaryOffset: number;
|
|
23
|
+
/**
|
|
24
|
+
* Offset from UTC in minutes as observed in June (northern hemisphere summer).
|
|
25
|
+
*/
|
|
26
|
+
juneOffset: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns the full display label of a timezone, combining the offset and the name,
|
|
30
|
+
* e.g. "GMT+01:00 Prague". Use only where the merged label is actually displayed;
|
|
31
|
+
* consumers that need the name or the offset alone should read the fields directly.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export declare function getTimezoneTitle(item: Pick<ITimezoneItem, "name" | "offsetLabel">): string;
|
|
36
|
+
/**
|
|
37
|
+
* Returns the curated list of timezones.
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export declare function getTimezones(): ITimezoneItem[];
|
|
42
|
+
/**
|
|
43
|
+
* Returns the timezone from the curated list matching the provided IANA timezone ID,
|
|
44
|
+
* or undefined when the ID is not in the curated list.
|
|
45
|
+
*
|
|
46
|
+
* @internal
|
|
47
|
+
*/
|
|
48
|
+
export declare function getTimezoneById(id: string | undefined): ITimezoneItem | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Returns the curated timezone that matches the offsets of the browser/runtime timezone.
|
|
51
|
+
* Falls back to UTC when no curated timezone matches.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export declare function getUserTimezone(): ITimezoneItem;
|
|
56
|
+
/**
|
|
57
|
+
* Returns the display label used by the timezone picker: the friendly name with the GMT offset
|
|
58
|
+
* as a suffix in brackets, e.g. "Prague (GMT+01:00)". The name falls back to the raw IANA ID
|
|
59
|
+
* for zones outside the curated list, and the offset suffix is dropped when not known.
|
|
60
|
+
*
|
|
61
|
+
* The friendly name (not the IANA ID) is shown so the picker reads the same as the Home UI
|
|
62
|
+
* timezone settings — the id-to-name mapping is not visible to users anywhere.
|
|
63
|
+
*
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
66
|
+
export declare function getTimezoneDisplayLabel(id: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Tests whether the timezone matches the search string. Matches on the picker display label
|
|
69
|
+
* ("name (offset)"), the friendly title (offset + name) and the IANA ID, with underscores
|
|
70
|
+
* treated as spaces so that e.g. "Los Angeles" finds "America/Los_Angeles".
|
|
71
|
+
*
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export declare function timezoneMatchesSearch(timezone: ITimezoneItem, search: string): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Returns display labels for the provided IANA timezone ID.
|
|
77
|
+
*
|
|
78
|
+
* @remarks
|
|
79
|
+
* The backend accepts any ICU-known timezone, so the ID may not be present in the curated list.
|
|
80
|
+
* In that case the raw ID is used as the name and the offset label is computed via Intl
|
|
81
|
+
* (empty when the runtime does not know the timezone either).
|
|
82
|
+
*
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
export declare function getTimezoneLabels(id: string): Pick<ITimezoneItem, "name" | "offsetLabel">;
|
|
86
|
+
//# sourceMappingURL=timezones.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timezones.d.ts","sourceRoot":"","sources":["../../src/Timezone/timezones.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC1B;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;CACtB;AAkJD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,MAAM,CAE1F;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,SAAS,CAEjF;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,aAAa,CAkB/C;AAED;;;;;;;;;GASG;AAGH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAWtF;AAmBD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,CAAC,CAUzF"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
const timezonesRaw = [
|
|
3
|
+
["Pacific/Midway", "International Date Line West", "GMT-11:00", -660, -660],
|
|
4
|
+
["Pacific/Pago_Pago", "Samoa", "GMT-11:00", -660, -660],
|
|
5
|
+
["Pacific/Honolulu", "Hawaii", "GMT-10:00", -600, -600],
|
|
6
|
+
["America/Juneau", "Alaska", "GMT-09:00", -540, -480],
|
|
7
|
+
["America/Los_Angeles", "Pacific Time (US & Canada)", "GMT-08:00", -480, -420],
|
|
8
|
+
["America/Tijuana", "Tijuana", "GMT-08:00", -480, -420],
|
|
9
|
+
["America/Phoenix", "Arizona", "GMT-07:00", -420, -420],
|
|
10
|
+
["America/Chihuahua", "Chihuahua", "GMT-07:00", -420, -360],
|
|
11
|
+
["America/Mazatlan", "Mazatlan", "GMT-07:00", -420, -360],
|
|
12
|
+
["America/Denver", "Mountain Time (US & Canada)", "GMT-07:00", -420, -360],
|
|
13
|
+
["America/Guatemala", "Central America", "GMT-06:00", -360, -360],
|
|
14
|
+
["America/Chicago", "Central Time (US & Canada)", "GMT-06:00", -360, -300],
|
|
15
|
+
["America/Winnipeg", "Central - ON (west); Manitoba", "GMT-06:00", -360, -300],
|
|
16
|
+
["America/Mexico_City", "Mexico City", "GMT-06:00", -360, -300],
|
|
17
|
+
["America/Monterrey", "Monterrey", "GMT-06:00", -360, -300],
|
|
18
|
+
["America/Regina", "Saskatchewan", "GMT-06:00", -360, -360],
|
|
19
|
+
["America/Bogota", "Bogota", "GMT-05:00", -300, -300],
|
|
20
|
+
["America/New_York", "Eastern Time (US & Canada)", "GMT-05:00", -300, -240],
|
|
21
|
+
["America/Indiana/Indianapolis", "Indiana (East)", "GMT-05:00", -300, -240],
|
|
22
|
+
["America/Lima", "Lima", "GMT-05:00", -300, -300],
|
|
23
|
+
["America/Caracas", "Caracas", "GMT-04:30", -270, -270],
|
|
24
|
+
["America/Halifax", "Atlantic Time (Canada)", "GMT-04:00", -240, -180],
|
|
25
|
+
["America/Guyana", "Georgetown", "GMT-04:00", -240, -240],
|
|
26
|
+
["America/La_Paz", "La Paz", "GMT-04:00", -240, -240],
|
|
27
|
+
["America/Santiago", "Santiago", "GMT-04:00", -180, -240],
|
|
28
|
+
["America/St_Johns", "Newfoundland", "GMT-03:30", -210, -150],
|
|
29
|
+
["America/Sao_Paulo", "Brasilia", "GMT-03:00", -120, -180],
|
|
30
|
+
["America/Argentina/Buenos_Aires", "Buenos Aires", "GMT-03:00", -180, -180],
|
|
31
|
+
["America/Godthab", "Greenland", "GMT-03:00", -180, -120],
|
|
32
|
+
["Atlantic/South_Georgia", "Mid-Atlantic", "GMT-02:00", -120, -120],
|
|
33
|
+
["Atlantic/Azores", "Azores", "GMT-01:00", -60, 0],
|
|
34
|
+
["Atlantic/Cape_Verde", "Cape Verde Is.", "GMT-01:00", -60, -60],
|
|
35
|
+
["Africa/Casablanca", "Casablanca", "GMT+00:00", 0, 0],
|
|
36
|
+
["Europe/Dublin", "Dublin", "GMT+00:00", 0, 60],
|
|
37
|
+
["Europe/Lisbon", "Lisbon", "GMT+00:00", 0, 60],
|
|
38
|
+
["Europe/London", "London", "GMT+00:00", 0, 60],
|
|
39
|
+
["Africa/Monrovia", "Monrovia", "GMT+00:00", 0, 0],
|
|
40
|
+
["Etc/UTC", "UTC", "GMT+00:00", 0, 0],
|
|
41
|
+
["Europe/Amsterdam", "Amsterdam", "GMT+01:00", 60, 120],
|
|
42
|
+
["Europe/Belgrade", "Belgrade", "GMT+01:00", 60, 120],
|
|
43
|
+
["Europe/Berlin", "Berlin", "GMT+01:00", 60, 120],
|
|
44
|
+
["Europe/Bratislava", "Bratislava", "GMT+01:00", 60, 120],
|
|
45
|
+
["Europe/Brussels", "Brussels", "GMT+01:00", 60, 120],
|
|
46
|
+
["Europe/Budapest", "Budapest", "GMT+01:00", 60, 120],
|
|
47
|
+
["Europe/Copenhagen", "Copenhagen", "GMT+01:00", 60, 120],
|
|
48
|
+
["Europe/Ljubljana", "Ljubljana", "GMT+01:00", 60, 120],
|
|
49
|
+
["Europe/Madrid", "Madrid", "GMT+01:00", 60, 120],
|
|
50
|
+
["Europe/Paris", "Paris", "GMT+01:00", 60, 120],
|
|
51
|
+
["Europe/Prague", "Prague", "GMT+01:00", 60, 120],
|
|
52
|
+
["Europe/Rome", "Rome", "GMT+01:00", 60, 120],
|
|
53
|
+
["Europe/Sarajevo", "Sarajevo", "GMT+01:00", 60, 120],
|
|
54
|
+
["Europe/Skopje", "Skopje", "GMT+01:00", 60, 120],
|
|
55
|
+
["Europe/Stockholm", "Stockholm", "GMT+01:00", 60, 120],
|
|
56
|
+
["Europe/Vienna", "Vienna", "GMT+01:00", 60, 120],
|
|
57
|
+
["Europe/Warsaw", "Warsaw", "GMT+01:00", 60, 120],
|
|
58
|
+
["Africa/Algiers", "West Central Africa", "GMT+01:00", 60, 60],
|
|
59
|
+
["Europe/Zagreb", "Zagreb", "GMT+01:00", 60, 120],
|
|
60
|
+
["Europe/Athens", "Athens", "GMT+02:00", 120, 180],
|
|
61
|
+
["Europe/Bucharest", "Bucharest", "GMT+02:00", 120, 180],
|
|
62
|
+
["Africa/Cairo", "Cairo", "GMT+02:00", 120, 180],
|
|
63
|
+
["Africa/Harare", "Harare", "GMT+02:00", 120, 120],
|
|
64
|
+
["Europe/Helsinki", "Helsinki", "GMT+02:00", 120, 180],
|
|
65
|
+
["Europe/Istanbul", "Istanbul", "GMT+02:00", 120, 180],
|
|
66
|
+
["Asia/Jerusalem", "Jerusalem", "GMT+02:00", 120, 180],
|
|
67
|
+
["Europe/Kiev", "Kyiv", "GMT+02:00", 120, 180],
|
|
68
|
+
["Europe/Minsk", "Minsk", "GMT+02:00", 120, 180],
|
|
69
|
+
["Africa/Johannesburg", "Pretoria", "GMT+02:00", 120, 120],
|
|
70
|
+
["Europe/Riga", "Riga", "GMT+02:00", 120, 180],
|
|
71
|
+
["Europe/Sofia", "Sofia", "GMT+02:00", 120, 180],
|
|
72
|
+
["Europe/Tallinn", "Tallinn", "GMT+02:00", 120, 180],
|
|
73
|
+
["Europe/Vilnius", "Vilnius", "GMT+02:00", 120, 180],
|
|
74
|
+
["Asia/Baghdad", "Baghdad", "GMT+03:00", 180, 180],
|
|
75
|
+
["Asia/Kuwait", "Kuwait", "GMT+03:00", 180, 180],
|
|
76
|
+
["Europe/Moscow", "Moscow", "GMT+03:00", 180, 240],
|
|
77
|
+
["Africa/Nairobi", "Nairobi", "GMT+03:00", 180, 180],
|
|
78
|
+
["Asia/Riyadh", "Riyadh", "GMT+03:00", 180, 180],
|
|
79
|
+
["Asia/Tehran", "Tehran", "GMT+03:30", 210, 270],
|
|
80
|
+
["Asia/Baku", "Baku", "GMT+04:00", 240, 300],
|
|
81
|
+
["Asia/Muscat", "Muscat", "GMT+04:00", 240, 240],
|
|
82
|
+
["Asia/Tbilisi", "Tbilisi", "GMT+04:00", 240, 240],
|
|
83
|
+
["Asia/Yerevan", "Yerevan", "GMT+04:00", 240, 300],
|
|
84
|
+
["Asia/Kabul", "Kabul", "GMT+04:30", 270, 270],
|
|
85
|
+
["Asia/Yekaterinburg", "Ekaterinburg", "GMT+05:00", 300, 360],
|
|
86
|
+
["Asia/Karachi", "Karachi", "GMT+05:00", 300, 300],
|
|
87
|
+
["Asia/Tashkent", "Tashkent", "GMT+05:00", 300, 300],
|
|
88
|
+
["Asia/Kolkata", "Kolkata", "GMT+05:30", 330, 330],
|
|
89
|
+
["Asia/Colombo", "Sri Jayawardenepura", "GMT+05:30", 330, 330],
|
|
90
|
+
["Asia/Kathmandu", "Kathmandu", "GMT+05:45", 345, 345],
|
|
91
|
+
["Asia/Almaty", "Almaty", "GMT+06:00", 360, 360],
|
|
92
|
+
["Asia/Dhaka", "Dhaka", "GMT+06:00", 360, 360],
|
|
93
|
+
["Asia/Novosibirsk", "Novosibirsk", "GMT+06:00", 360, 420],
|
|
94
|
+
["Asia/Rangoon", "Rangoon", "GMT+06:30", 390, 390],
|
|
95
|
+
["Asia/Bangkok", "Bangkok", "GMT+07:00", 420, 420],
|
|
96
|
+
["Asia/Jakarta", "Jakarta", "GMT+07:00", 420, 420],
|
|
97
|
+
["Asia/Krasnoyarsk", "Krasnoyarsk", "GMT+07:00", 420, 480],
|
|
98
|
+
["Asia/Shanghai", "Beijing", "GMT+08:00", 480, 480],
|
|
99
|
+
["Asia/Chongqing", "Chongqing", "GMT+08:00", 480, 480],
|
|
100
|
+
["Asia/Hong_Kong", "Hong Kong", "GMT+08:00", 480, 480],
|
|
101
|
+
["Asia/Irkutsk", "Irkutsk", "GMT+08:00", 480, 540],
|
|
102
|
+
["Asia/Kuala_Lumpur", "Kuala Lumpur", "GMT+08:00", 480, 480],
|
|
103
|
+
["Australia/Perth", "Perth", "GMT+08:00", 480, 480],
|
|
104
|
+
["Asia/Singapore", "Singapore", "GMT+08:00", 480, 480],
|
|
105
|
+
["Asia/Taipei", "Taipei", "GMT+08:00", 480, 480],
|
|
106
|
+
["Asia/Ulaanbaatar", "Ulaanbaatar", "GMT+08:00", 480, 480],
|
|
107
|
+
["Asia/Urumqi", "Urumqi", "GMT+08:00", 480, 480],
|
|
108
|
+
["Asia/Seoul", "Seoul", "GMT+09:00", 540, 540],
|
|
109
|
+
["Asia/Tokyo", "Tokyo", "GMT+09:00", 540, 540],
|
|
110
|
+
["Asia/Yakutsk", "Yakutsk", "GMT+09:00", 540, 600],
|
|
111
|
+
["Australia/Adelaide", "Adelaide", "GMT+09:30", 630, 570],
|
|
112
|
+
["Australia/Darwin", "Darwin", "GMT+09:30", 570, 570],
|
|
113
|
+
["Australia/Brisbane", "Brisbane", "GMT+10:00", 600, 600],
|
|
114
|
+
["Pacific/Guam", "Guam", "GMT+10:00", 600, 600],
|
|
115
|
+
["Australia/Hobart", "Hobart", "GMT+10:00", 660, 600],
|
|
116
|
+
["Australia/Melbourne", "Melbourne", "GMT+10:00", 660, 600],
|
|
117
|
+
["Pacific/Port_Moresby", "Port Moresby", "GMT+10:00", 600, 600],
|
|
118
|
+
["Australia/Sydney", "Sydney", "GMT+10:00", 660, 600],
|
|
119
|
+
["Asia/Vladivostok", "Vladivostok", "GMT+10:00", 600, 660],
|
|
120
|
+
["Asia/Kamchatka", "Kamchatka", "GMT+11:00", 660, 720],
|
|
121
|
+
["Asia/Magadan", "Magadan", "GMT+11:00", 660, 720],
|
|
122
|
+
["Pacific/Noumea", "New Caledonia", "GMT+11:00", 660, 660],
|
|
123
|
+
["Asia/Sakhalin", "Sakhalin Island", "GMT+11:00", 660, 660],
|
|
124
|
+
["Pacific/Auckland", "Auckland", "GMT+12:00", 780, 720],
|
|
125
|
+
["Pacific/Fiji", "Fiji", "GMT+12:00", 780, 720],
|
|
126
|
+
["Pacific/Majuro", "Marshall Is.", "GMT+12:00", 720, 720],
|
|
127
|
+
["Pacific/Tongatapu", "Nuku'alofa", "GMT+13:00", 780, 780],
|
|
128
|
+
];
|
|
129
|
+
const timezones = timezonesRaw.map(([id, name, offsetLabel, januaryOffset, juneOffset]) => ({
|
|
130
|
+
id,
|
|
131
|
+
name,
|
|
132
|
+
offsetLabel,
|
|
133
|
+
januaryOffset,
|
|
134
|
+
juneOffset,
|
|
135
|
+
}));
|
|
136
|
+
const TIMEZONE_DEFAULT = timezones.find((tz) => tz.id === "Etc/UTC");
|
|
137
|
+
/**
|
|
138
|
+
* Returns the full display label of a timezone, combining the offset and the name,
|
|
139
|
+
* e.g. "GMT+01:00 Prague". Use only where the merged label is actually displayed;
|
|
140
|
+
* consumers that need the name or the offset alone should read the fields directly.
|
|
141
|
+
*
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
export function getTimezoneTitle(item) {
|
|
145
|
+
return item.offsetLabel ? `${item.offsetLabel} ${item.name}` : item.name;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Returns the curated list of timezones.
|
|
149
|
+
*
|
|
150
|
+
* @internal
|
|
151
|
+
*/
|
|
152
|
+
export function getTimezones() {
|
|
153
|
+
return timezones;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Returns the timezone from the curated list matching the provided IANA timezone ID,
|
|
157
|
+
* or undefined when the ID is not in the curated list.
|
|
158
|
+
*
|
|
159
|
+
* @internal
|
|
160
|
+
*/
|
|
161
|
+
export function getTimezoneById(id) {
|
|
162
|
+
return timezones.find((tz) => tz.id === id);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Returns the curated timezone that matches the offsets of the browser/runtime timezone.
|
|
166
|
+
* Falls back to UTC when no curated timezone matches.
|
|
167
|
+
*
|
|
168
|
+
* @internal
|
|
169
|
+
*/
|
|
170
|
+
export function getUserTimezone() {
|
|
171
|
+
// prefer the exact runtime IANA id so zones sharing offsets (e.g. Berlin vs Amsterdam)
|
|
172
|
+
// resolve to the viewer's actual zone; fall back to offset matching for runtime ids
|
|
173
|
+
// that are not part of the curated list
|
|
174
|
+
const runtimeTimezoneId = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
175
|
+
const exactMatch = getTimezoneById(runtimeTimezoneId);
|
|
176
|
+
if (exactMatch) {
|
|
177
|
+
return exactMatch;
|
|
178
|
+
}
|
|
179
|
+
// sample the offsets on a fixed date in winter and in summer so that both DST variants are covered
|
|
180
|
+
const januaryOffset = -new Date(2011, 0, 1, 0, 0, 0, 0).getTimezoneOffset();
|
|
181
|
+
const juneOffset = -new Date(2011, 5, 1, 0, 0, 0, 0).getTimezoneOffset();
|
|
182
|
+
return (timezones.find((tz) => tz.januaryOffset === januaryOffset && tz.juneOffset === juneOffset) ??
|
|
183
|
+
TIMEZONE_DEFAULT);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Returns the display label used by the timezone picker: the friendly name with the GMT offset
|
|
187
|
+
* as a suffix in brackets, e.g. "Prague (GMT+01:00)". The name falls back to the raw IANA ID
|
|
188
|
+
* for zones outside the curated list, and the offset suffix is dropped when not known.
|
|
189
|
+
*
|
|
190
|
+
* The friendly name (not the IANA ID) is shown so the picker reads the same as the Home UI
|
|
191
|
+
* timezone settings — the id-to-name mapping is not visible to users anywhere.
|
|
192
|
+
*
|
|
193
|
+
* @internal
|
|
194
|
+
*/
|
|
195
|
+
// TODO INE (CQ-2725): finalize the picker label format and the ID-in-tooltip presentation
|
|
196
|
+
// after the UX discussion.
|
|
197
|
+
export function getTimezoneDisplayLabel(id) {
|
|
198
|
+
const { name, offsetLabel } = getTimezoneLabels(id);
|
|
199
|
+
return offsetLabel ? `${name} (${offsetLabel})` : name;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Tests whether the timezone matches the search string. Matches on the picker display label
|
|
203
|
+
* ("name (offset)"), the friendly title (offset + name) and the IANA ID, with underscores
|
|
204
|
+
* treated as spaces so that e.g. "Los Angeles" finds "America/Los_Angeles".
|
|
205
|
+
*
|
|
206
|
+
* @internal
|
|
207
|
+
*/
|
|
208
|
+
export function timezoneMatchesSearch(timezone, search) {
|
|
209
|
+
const normalizedSearch = search.trim().toLowerCase();
|
|
210
|
+
if (!normalizedSearch) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
return (getTimezoneTitle(timezone).toLowerCase().includes(normalizedSearch) ||
|
|
214
|
+
getTimezoneDisplayLabel(timezone.id).toLowerCase().includes(normalizedSearch) ||
|
|
215
|
+
timezone.id.toLowerCase().replace(/_/g, " ").includes(normalizedSearch.replace(/_/g, " ")));
|
|
216
|
+
}
|
|
217
|
+
function computeOffsetLabel(id) {
|
|
218
|
+
try {
|
|
219
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
220
|
+
timeZone: id,
|
|
221
|
+
timeZoneName: "longOffset",
|
|
222
|
+
}).formatToParts(new Date());
|
|
223
|
+
const offsetPart = parts.find((part) => part.type === "timeZoneName")?.value;
|
|
224
|
+
if (!offsetPart) {
|
|
225
|
+
return "";
|
|
226
|
+
}
|
|
227
|
+
// plain "GMT" is reported for the zero offset; normalize to the curated label format
|
|
228
|
+
return offsetPart === "GMT" ? "GMT+00:00" : offsetPart;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return "";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Returns display labels for the provided IANA timezone ID.
|
|
236
|
+
*
|
|
237
|
+
* @remarks
|
|
238
|
+
* The backend accepts any ICU-known timezone, so the ID may not be present in the curated list.
|
|
239
|
+
* In that case the raw ID is used as the name and the offset label is computed via Intl
|
|
240
|
+
* (empty when the runtime does not know the timezone either).
|
|
241
|
+
*
|
|
242
|
+
* @internal
|
|
243
|
+
*/
|
|
244
|
+
export function getTimezoneLabels(id) {
|
|
245
|
+
const known = getTimezoneById(id);
|
|
246
|
+
if (known) {
|
|
247
|
+
return { name: known.name, offsetLabel: known.offsetLabel };
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
name: id,
|
|
251
|
+
offsetLabel: computeOffsetLabel(id),
|
|
252
|
+
};
|
|
253
|
+
}
|