@gooddata/sdk-ui-kit 11.36.0-alpha.3 → 11.36.0-alpha.7

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 (36) hide show
  1. package/esm/@ui/UiControlButton/UiControlButton.d.ts +5 -1
  2. package/esm/@ui/UiControlButton/UiControlButton.js +2 -2
  3. package/esm/@ui/UiFocusManager/UiAutofocus.js +74 -18
  4. package/esm/@ui/UiMenu/hooks.js +14 -4
  5. package/esm/@ui/UiMenu/types.d.ts +4 -0
  6. package/esm/@ui/UiPagedVirtualList/UiPagedVirtualList.d.ts +1 -1
  7. package/esm/@ui/UiPagedVirtualList/UiPagedVirtualList.js +9 -3
  8. package/esm/Dialog/ShareDialog/ShareDialog.d.ts +1 -1
  9. package/esm/Dialog/ShareDialog/ShareDialog.js +3 -3
  10. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareDialogBase.js +2 -2
  11. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.d.ts +1 -1
  12. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.js +2 -2
  13. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.d.ts +1 -1
  14. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.js +9 -4
  15. package/esm/Dialog/ShareDialog/ShareDialogBase/types.d.ts +4 -1
  16. package/esm/Dialog/ShareDialog/ShareDialogBase/types.js +1 -1
  17. package/esm/Dialog/ShareDialog/types.d.ts +2 -1
  18. package/esm/Dialog/ShareDialog/types.js +1 -0
  19. package/esm/Dropdown/DropdownList.js +12 -4
  20. package/esm/FilterGroupItem/FilterGroupItem.js +12 -5
  21. package/esm/Header/generateHeaderMenuItemsGroups.js +10 -2
  22. package/esm/Icon/Icon.js +3 -1
  23. package/esm/Icon/InsightIcon.js +2 -1
  24. package/esm/Icon/icons/InsightIcons/Radar.d.ts +5 -0
  25. package/esm/Icon/icons/InsightIcons/Radar.js +10 -0
  26. package/esm/index.d.ts +1 -0
  27. package/esm/index.js +1 -0
  28. package/esm/sdk-ui-kit.d.ts +21 -4
  29. package/package.json +11 -11
  30. package/styles/css/insightList.css +6 -2
  31. package/styles/css/insightList.css.map +1 -1
  32. package/styles/css/main.css +6 -2
  33. package/styles/css/main.css.map +1 -1
  34. package/styles/images/visualization-types/radar-active.svg +7 -0
  35. package/styles/images/visualization-types/radar.svg +7 -0
  36. package/styles/scss/insightList.scss +2 -2
@@ -25,6 +25,10 @@ export interface IUiControlButtonProps {
25
25
  buttonRef?: Ref<HTMLDivElement>;
26
26
  buttonId?: string;
27
27
  dropdownId?: string;
28
+ /**
29
+ * Overrides the accessible name. When omitted, the name is derived from the rendered title.
30
+ */
31
+ ariaLabel?: string;
28
32
  }
29
33
  /**
30
34
  * Generic chip-shaped dropdown trigger primitive. One source of truth for filter-bar chip
@@ -36,4 +40,4 @@ export interface IUiControlButtonProps {
36
40
  *
37
41
  * @internal
38
42
  */
39
- export declare function UiControlButton({ title, titleClassName, subtitle, icon, titleExtension, subtitleExtension, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId }: IUiControlButtonProps): import("react/jsx-runtime").JSX.Element;
43
+ export declare function UiControlButton({ title, titleClassName, subtitle, icon, titleExtension, subtitleExtension, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel }: IUiControlButtonProps): import("react/jsx-runtime").JSX.Element;
@@ -21,7 +21,7 @@ const TITLE_TOOLTIP_ALIGN_POINTS = [
21
21
  *
22
22
  * @internal
23
23
  */
24
- export function UiControlButton({ title, titleClassName, subtitle, icon, titleExtension, subtitleExtension, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, }) {
24
+ export function UiControlButton({ title, titleClassName, subtitle, icon, titleExtension, subtitleExtension, isOpen, isDraggable, isDragging, isError, disabled, disabledTooltip, onClick, className, "data-testid": dataTestId, buttonRef, buttonId, dropdownId, ariaLabel, }) {
25
25
  const tooltipId = useIdPrefixed("gd-ui-kit-control-button-tooltip");
26
26
  const showDisabledTooltip = !!disabled && !!disabledTooltip;
27
27
  const onKeyDown = (event) => {
@@ -41,7 +41,7 @@ export function UiControlButton({ title, titleClassName, subtitle, icon, titleEx
41
41
  isDragging: !!isDragging,
42
42
  isError: !!isError,
43
43
  disabled: !!disabled,
44
- }), className), role: "button", tabIndex: 0, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-disabled": disabled, "aria-controls": isOpen ? dropdownId : undefined, "aria-describedby": showDisabledTooltip ? tooltipId : undefined, "data-testid": dataTestId, onClick: disabled ? undefined : onClick, onKeyDown: onKeyDown, children: [icon ? _jsx("div", { className: e("icon"), children: icon }) : null, _jsxs("div", { className: e("content"), children: [
44
+ }), className), role: "button", tabIndex: 0, "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-disabled": disabled, "aria-controls": isOpen ? dropdownId : undefined, "aria-describedby": showDisabledTooltip ? tooltipId : undefined, "aria-label": ariaLabel, "data-testid": dataTestId, onClick: disabled ? undefined : onClick, onKeyDown: onKeyDown, children: [icon ? _jsx("div", { className: e("icon"), children: icon }) : null, _jsxs("div", { className: e("content"), children: [
45
45
  _jsxs("div", { className: e("title-row"), children: [
46
46
  _jsx("div", { className: e("title"), children: _jsx(ShortenedText, { tooltipAlignPoints: TITLE_TOOLTIP_ALIGN_POINTS, className: titleClassName, children: title }) }), titleExtension] }), subtitle === undefined && subtitleExtension === undefined ? null : (_jsxs("div", { className: e("subtitle-row"), children: [typeof subtitle === "string" ? (_jsx("span", { className: e("subtitle"), children: subtitle })) : (subtitle), subtitleExtension] }))] })
47
47
  ] }));
@@ -18,30 +18,86 @@ export const useUiAutofocusConnectors = ({ active = true, refocusKey, initialFoc
18
18
  setElement(el);
19
19
  }
20
20
  }, []);
21
- // If the element is outside of the viewport, calling focus() will not work.
22
- // This can happen for example with floating elements, that are repositioned after they mount
21
+ // Focus the first focusable inside the wrapper. Robust against slow renders, virtualized lists
22
+ // that recycle DOM nodes, and floating overlays that finish positioning after mount.
23
+ //
24
+ // `.focus()` is a no-op on elements that are not currently focusable (e.g. inside an ancestor
25
+ // with `visibility: hidden`, which Overlay applies until alignment completes). The browser
26
+ // does not throw or fire `focusin` in that case. To handle this we watch two signals:
27
+ //
28
+ // - IntersectionObserver observes the resolved TARGET — fires when the target moves into the
29
+ // viewport (e.g. an Overlay completing alignment from off-screen to its real position).
30
+ // This is the moment `.focus()` will start taking.
31
+ // - MutationObserver observes the wrapper — fires on descendant additions/removals
32
+ // (lazy mount, virtualization) and on attribute changes (style/class/inert/hidden/...)
33
+ // that may affect focusability without a position change.
34
+ //
35
+ // Each signal re-runs `attemptFocus`, which re-resolves the target and re-attaches the
36
+ // IntersectionObserver to it. A single rAF coalesces bursts into one attempt per frame.
23
37
  useEffect(() => {
24
- const elementToFocus = getElementToFocus(element, initialFocus, true);
25
- if (!elementToFocus || !active) {
38
+ if (!element || !active) {
26
39
  return undefined;
27
40
  }
28
- const observer = new IntersectionObserver(([{ target }]) => {
29
- // Focusing a newly created element sometimes fails if not done through requestAnimationFrame()
30
- window.requestAnimationFrame(() => {
31
- if (element?.contains(document.activeElement) ||
32
- target.contains(document.activeElement) ||
33
- isElementTextInput(document.activeElement)) {
34
- observer.disconnect();
35
- return;
36
- }
37
- target.focus();
38
- if (document.activeElement === target) {
39
- observer.disconnect();
41
+ let rafId = 0;
42
+ let stopped = false;
43
+ let observedTarget = null;
44
+ const intersectionObserver = new IntersectionObserver(() => {
45
+ scheduleAttempt();
46
+ });
47
+ const observeTarget = (newTarget) => {
48
+ if (newTarget === observedTarget) {
49
+ return;
50
+ }
51
+ if (observedTarget) {
52
+ intersectionObserver.unobserve(observedTarget);
53
+ }
54
+ observedTarget = newTarget ?? null;
55
+ if (observedTarget) {
56
+ intersectionObserver.observe(observedTarget);
57
+ }
58
+ };
59
+ const isFocusSettled = () => element.contains(document.activeElement) || isElementTextInput(document.activeElement);
60
+ const attemptFocus = () => {
61
+ if (isFocusSettled()) {
62
+ return true;
63
+ }
64
+ const target = getElementToFocus(element, initialFocus, true);
65
+ // Always (re-)observe the resolved target; the IntersectionObserver wakes us up when
66
+ // it moves into the viewport — the moment `.focus()` will take after Overlay alignment.
67
+ observeTarget(target);
68
+ if (!target?.isConnected) {
69
+ return false;
70
+ }
71
+ target.focus();
72
+ return document.activeElement === target;
73
+ };
74
+ const scheduleAttempt = () => {
75
+ if (stopped) {
76
+ return;
77
+ }
78
+ cancelAnimationFrame(rafId);
79
+ rafId = requestAnimationFrame(() => {
80
+ if (attemptFocus()) {
81
+ stop();
40
82
  }
41
83
  });
84
+ };
85
+ const stop = () => {
86
+ stopped = true;
87
+ cancelAnimationFrame(rafId);
88
+ mutationObserver.disconnect();
89
+ intersectionObserver.disconnect();
90
+ };
91
+ const mutationObserver = new MutationObserver(scheduleAttempt);
92
+ mutationObserver.observe(element, {
93
+ childList: true,
94
+ subtree: true,
95
+ attributes: true,
96
+ attributeFilter: ["style", "class", "hidden", "inert", "disabled", "tabindex", "aria-hidden"],
42
97
  });
43
- observer.observe(elementToFocus);
44
- return () => observer.disconnect();
98
+ // Initial attempt — fast path when the first focusable is already present and visible.
99
+ scheduleAttempt();
100
+ return stop;
45
101
  }, [refocusKey, element, initialFocus, active]);
46
102
  return useMemo(() => ({ ref }), [ref]);
47
103
  };
@@ -16,7 +16,7 @@ import { getClosestFocusableSibling, getFocusableItem, getItemInteractiveParent,
16
16
  * @internal
17
17
  */
18
18
  export function useUiMenuContextValue(props, menuComponentRef, itemsContainerRef) {
19
- const { items, size = "medium", itemDataTestId, onSelect, onLevelChange, onClose, InteractiveItem: InteractiveItemComponent = DefaultUiMenuInteractiveItem, InteractiveItemWrapper: InteractiveItemWrapperComponent = DefaultUiMenuInteractiveItemWrapper, StaticItem: StaticItemComponent = DefaultUiMenuStaticItem, GroupItem: GroupItemComponent = DefaultUiMenuGroupItem, MenuHeader: MenuHeaderComponent = DefaultUiMenuHeader, ContentItem: ContentItemComponent = DefaultUiMenuContentItem, ContentItemWrapper: ContentItemWrapperComponent = DefaultUiMenuContentItemWrapper, Content: ContentComponent = DefaultUiMenuContent, shouldCloseOnSelect = true, isDisabledFocusable = true, ariaAttributes, menuCtxData, } = props;
19
+ const { items, size = "medium", itemDataTestId, onSelect, onLeaveLevel, onEnterLevel, onLevelChange, onClose, InteractiveItem: InteractiveItemComponent = DefaultUiMenuInteractiveItem, InteractiveItemWrapper: InteractiveItemWrapperComponent = DefaultUiMenuInteractiveItemWrapper, StaticItem: StaticItemComponent = DefaultUiMenuStaticItem, GroupItem: GroupItemComponent = DefaultUiMenuGroupItem, MenuHeader: MenuHeaderComponent = DefaultUiMenuHeader, ContentItem: ContentItemComponent = DefaultUiMenuContentItem, ContentItemWrapper: ContentItemWrapperComponent = DefaultUiMenuContentItemWrapper, Content: ContentComponent = DefaultUiMenuContent, shouldCloseOnSelect = true, isDisabledFocusable = true, ariaAttributes, menuCtxData, } = props;
20
20
  const [controlType, setControlType] = useState("unknown");
21
21
  const isItemFocusable = useCallback((item) => {
22
22
  if (!item || (item.type !== "interactive" && item.type !== "content")) {
@@ -128,6 +128,8 @@ export function useUiMenuContextValue(props, menuComponentRef, itemsContainerRef
128
128
  items,
129
129
  size,
130
130
  onSelect: handleSelectItem,
131
+ onLeaveLevel,
132
+ onEnterLevel,
131
133
  itemDataTestId,
132
134
  isItemFocusable,
133
135
  makeItemId,
@@ -171,6 +173,8 @@ export function useUiMenuContextValue(props, menuComponentRef, itemsContainerRef
171
173
  shownCustomContentItemId,
172
174
  shownSubview,
173
175
  size,
176
+ onLeaveLevel,
177
+ onEnterLevel,
174
178
  ]);
175
179
  }
176
180
  /**
@@ -222,7 +226,10 @@ export function useKeyNavigation({ menuContextValue, shouldKeyboardActionPrevent
222
226
  onSelect(focusedItem, e);
223
227
  },
224
228
  onEnterLevel: (e) => {
225
- const { onSelect, focusedItem } = menuContextRef.current;
229
+ const { onSelect, onEnterLevel, focusedItem } = menuContextRef.current;
230
+ if (onEnterLevel?.(focusedItem, e)) {
231
+ return;
232
+ }
226
233
  if ((focusedItem?.type !== "interactive" && focusedItem?.type !== "content") ||
227
234
  (focusedItem?.type === "interactive" && !focusedItem.subItems) ||
228
235
  (focusedItem?.type === "content" && !focusedItem.Component)) {
@@ -230,8 +237,11 @@ export function useKeyNavigation({ menuContextValue, shouldKeyboardActionPrevent
230
237
  }
231
238
  onSelect(focusedItem, e);
232
239
  },
233
- onLeaveLevel: () => {
234
- const { setFocusedId, items } = menuContextRef.current;
240
+ onLeaveLevel: (e) => {
241
+ const { setFocusedId, onLeaveLevel, focusedItem, items } = menuContextRef.current;
242
+ if (onLeaveLevel?.(focusedItem, e)) {
243
+ return;
244
+ }
235
245
  setFocusedId((prevId) => {
236
246
  if (prevId === undefined) {
237
247
  return prevId;
@@ -156,6 +156,8 @@ export interface IUiMenuContext<T extends IUiMenuItemData = object, M = object>
156
156
  pushShownSubview: (subview: IUiMenuSubview) => void;
157
157
  popShownSubview: () => void;
158
158
  onSelect: (item: IUiMenuFocusableItem<T> | undefined, event: MouseEvent | KeyboardEvent) => void;
159
+ onEnterLevel?: (item: IUiMenuFocusableItem<T> | undefined, event: MouseEvent | KeyboardEvent) => boolean;
160
+ onLeaveLevel?: (item: IUiMenuFocusableItem<T> | undefined, event: MouseEvent | KeyboardEvent) => boolean;
159
161
  onClose?: () => void;
160
162
  setFocusedId: Dispatch<SetStateAction<string | undefined>>;
161
163
  isItemFocusable: (item: IUiMenuItem<T>) => boolean;
@@ -187,6 +189,8 @@ export interface IUiMenuProps<T extends IUiMenuItemData = object, M = object> ex
187
189
  containerBottomPadding?: "none" | "small" | "medium";
188
190
  containerTopPadding?: "none" | "small" | "medium";
189
191
  onSelect?: (item: IUiMenuInteractiveItem<T>, event: MouseEvent | KeyboardEvent) => void;
192
+ onEnterLevel?: (item: IUiMenuFocusableItem<T> | undefined, event: MouseEvent | KeyboardEvent) => boolean;
193
+ onLeaveLevel?: (item: IUiMenuFocusableItem<T> | undefined, event: MouseEvent | KeyboardEvent) => boolean;
190
194
  onLevelChange?: (level: number, item?: IUiMenuContentItem<T> | IUiMenuInteractiveItem<T>) => void;
191
195
  onClose?: () => void;
192
196
  onUnhandledKeyDown?: (event: KeyboardEvent, context: IUiMenuContext<T>) => void;
@@ -40,7 +40,7 @@ export interface IUiPagedVirtualListProps<T> {
40
40
  children: (item: T, focusedIndex?: number) => ReactNode;
41
41
  scrollbarHoverEffect?: boolean;
42
42
  SkeletonItem?: ComponentType<IUiPagedVirtualListSkeletonItemProps>;
43
- representAs?: "grid" | "listbox";
43
+ representAs?: "grid" | "listbox" | "list";
44
44
  listboxProps?: Record<string, any>;
45
45
  tabIndex?: number;
46
46
  focusedItem?: T;
@@ -67,11 +67,15 @@ function UiPagedVirtualListNotWrapped(props, ref) {
67
67
  height: `${rowVirtualizer.getTotalSize()}px`,
68
68
  width: "100%",
69
69
  position: "relative",
70
- }, tabIndex: tabIndex, onFocus: onFocus, onKeyDown: tabIndex < 0 ? undefined : (customKeyboardNavigationHandler ?? onKeyboardNavigation), role: representAs === "grid"
70
+ }, tabIndex: representAs === "list" ? undefined : tabIndex, onFocus: representAs === "list" ? undefined : onFocus, onKeyDown: representAs === "list" || tabIndex < 0
71
+ ? undefined
72
+ : (customKeyboardNavigationHandler ?? onKeyboardNavigation), role: representAs === "grid"
71
73
  ? "rowgroup"
72
74
  : representAs === "listbox"
73
75
  ? "listbox"
74
- : undefined, ...listboxProps, children: virtualItems.map((virtualRow) => {
76
+ : representAs === "list"
77
+ ? "list"
78
+ : undefined, ...listboxProps, children: virtualItems.map((virtualRow) => {
75
79
  const item = items?.[virtualRow.index];
76
80
  const isSkeletonItem = virtualRow.index > itemsCount - 1;
77
81
  const baseItemHeight = virtualRow.index >= itemsCount
@@ -97,7 +101,9 @@ function UiPagedVirtualListNotWrapped(props, ref) {
97
101
  ? makeId?.({ item, specifier: SELECT_ITEM_ACTION })
98
102
  : undefined,
99
103
  }
100
- : {};
104
+ : representAs === "list"
105
+ ? { role: "listitem" }
106
+ : {};
101
107
  return (_jsxs(ItemElement, { className: e("item", {
102
108
  isFocused: !customKeyboardNavigationHandler &&
103
109
  finalFocusedIndex === virtualRow.index,
@@ -2,4 +2,4 @@ import { type IShareDialogProps } from "./types.js";
2
2
  /**
3
3
  * @internal
4
4
  */
5
- export declare function ShareDialog({ backend, workspace, locale, sharedObject, currentUser, onApply, onCancel, onError, onInteraction, isLockingSupported, isCurrentUserWorkspaceManager, isGranteeShareLoading, labels, currentUserPermissions, dashboardFilters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, onShareLinkCopy }: IShareDialogProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function ShareDialog({ backend, workspace, locale, sharedObject, currentUser, onApply, onCancel, onError, onInteraction, isLockingSupported, isCurrentUserWorkspaceManager, isGranteeShareLoading, labels, currentUserPermissions, dashboardFilters, dashboardParameters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, onShareLinkCopy }: IShareDialogProps): import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // (C) 2021-2025 GoodData Corporation
2
+ // (C) 2021-2026 GoodData Corporation
3
3
  import { useCallback, useMemo } from "react";
4
4
  import { BackendProvider, IntlWrapper, UnexpectedSdkError, WorkspaceProvider, useBackendStrict, useWorkspaceStrict, } from "@gooddata/sdk-ui";
5
5
  import { ComponentInteractionProvider } from "./ShareDialogBase/ComponentInteractionContext.js";
@@ -9,7 +9,7 @@ import { mapGranteesToGranularAccessGrantees, mapGranteesToShareStatus, mapOwner
9
9
  /**
10
10
  * @internal
11
11
  */
12
- export function ShareDialog({ backend, workspace, locale, sharedObject, currentUser, onApply, onCancel, onError, onInteraction = () => { }, isLockingSupported, isCurrentUserWorkspaceManager, isGranteeShareLoading, labels, currentUserPermissions, dashboardFilters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, onShareLinkCopy, }) {
12
+ export function ShareDialog({ backend, workspace, locale, sharedObject, currentUser, onApply, onCancel, onError, onInteraction = () => { }, isLockingSupported, isCurrentUserWorkspaceManager, isGranteeShareLoading, labels, currentUserPermissions, dashboardFilters, dashboardParameters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, onShareLinkCopy, }) {
13
13
  const effectiveBackend = useBackendStrict(backend);
14
14
  const effectiveWorkspace = useWorkspaceStrict(workspace);
15
15
  const areGranularPermissionsSupported = effectiveBackend.capabilities.supportsGranularAccessControl;
@@ -54,5 +54,5 @@ export function ShareDialog({ backend, workspace, locale, sharedObject, currentU
54
54
  isMetadataObjectLockingSupported,
55
55
  canWorkspaceManagerSeeEverySharedObject,
56
56
  ]);
57
- return (_jsx(IntlWrapper, { locale: locale, children: _jsx(BackendProvider, { backend: effectiveBackend, children: _jsx(WorkspaceProvider, { workspace: effectiveWorkspace, children: _jsx(ComponentLabelsProvider, { labels: labels, children: _jsx(ComponentInteractionProvider, { onInteraction: onInteraction, currentUser: currentUser, currentUserPermissions: currentUserPermissions, isCurrentUserWorkspaceManager: isCurrentUserWorkspaceManager, sharedObjectStatus: affectedSharedObject.shareStatus, isSharedObjectLocked: affectedSharedObject.isLocked, children: _jsx(ShareDialogBase, { currentUser: currentUser, sharedObject: affectedSharedObject, isCurrentUserWorkspaceManager: isCurrentUserWorkspaceManager, currentUserPermissions: currentUserPermissions, dashboardFilters: dashboardFilters, onCancel: onCancel, onSubmit: onSubmit, onError: onShareDialogBaseError, isShareGrantHidden: isShareGrantHidden, applyShareGrantOnSelect: applyShareGrantOnSelect, showDashboardShareLink: showDashboardShareLink, onShareLinkCopy: onShareLinkCopy, isGranteeShareLoading: isGranteeShareLoading }) }) }) }) }) }));
57
+ return (_jsx(IntlWrapper, { locale: locale, children: _jsx(BackendProvider, { backend: effectiveBackend, children: _jsx(WorkspaceProvider, { workspace: effectiveWorkspace, children: _jsx(ComponentLabelsProvider, { labels: labels, children: _jsx(ComponentInteractionProvider, { onInteraction: onInteraction, currentUser: currentUser, currentUserPermissions: currentUserPermissions, isCurrentUserWorkspaceManager: isCurrentUserWorkspaceManager, sharedObjectStatus: affectedSharedObject.shareStatus, isSharedObjectLocked: affectedSharedObject.isLocked, children: _jsx(ShareDialogBase, { currentUser: currentUser, sharedObject: affectedSharedObject, isCurrentUserWorkspaceManager: isCurrentUserWorkspaceManager, currentUserPermissions: currentUserPermissions, dashboardFilters: dashboardFilters, dashboardParameters: dashboardParameters, onCancel: onCancel, onSubmit: onSubmit, onError: onShareDialogBaseError, isShareGrantHidden: isShareGrantHidden, applyShareGrantOnSelect: applyShareGrantOnSelect, showDashboardShareLink: showDashboardShareLink, onShareLinkCopy: onShareLinkCopy, isGranteeShareLoading: isGranteeShareLoading }) }) }) }) }) }));
58
58
  }
@@ -11,7 +11,7 @@ const alignPoints = [{ align: "cc cc" }];
11
11
  * @internal
12
12
  */
13
13
  export function ShareDialogBase(props) {
14
- const { onCancel, sharedObject, currentUser, currentUserPermissions, dashboardFilters, isCurrentUserWorkspaceManager, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, isGranteeShareLoading, onShareLinkCopy, } = props;
14
+ const { onCancel, sharedObject, currentUser, currentUserPermissions, dashboardFilters, dashboardParameters, isCurrentUserWorkspaceManager, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, isGranteeShareLoading, onShareLinkCopy, } = props;
15
15
  const { openInteraction, closeInteraction } = useShareDialogInteraction();
16
16
  useEffect(() => {
17
17
  openInteraction();
@@ -22,5 +22,5 @@ export function ShareDialogBase(props) {
22
22
  }, [onCancel, closeInteraction]);
23
23
  const { onAddedGranteeDelete, onSharedGranteeDelete, onAddGranteeBackClick, onAddGranteeButtonClick, onGranteeAdd, onSubmitShareGrantee, onSubmitAddGrantee, granteesToAdd, dialogMode, isShareDialogDirty, isAddDialogDirty, sharedGrantees, appliedGranteesWithOwner, isGranteesLoading, isLockedNow, isUnderLenientControlNow, onLockChange, onUnderLenientControlChange, onGranularGranteeAddChange, onGranularGranteeShareChange, } = useShareDialogBase(props);
24
24
  const previouslyFocusedRef = useRef(document.activeElement);
25
- return (_jsx(Overlay, { alignPoints: alignPoints, isModal: true, positionType: "fixed", className: "gd-share-dialog-overlay", children: _jsx("div", { className: "s-gd-share-dialog", children: dialogMode === "ShareGrantee" ? (_jsx(ShareGranteeBase, { currentUserPermissions: currentUserPermissions, dashboardFilters: dashboardFilters, isShareGrantHidden: isShareGrantHidden, applyShareGrantOnSelect: applyShareGrantOnSelect, showDashboardShareLink: showDashboardShareLink, onShareLinkCopy: onShareLinkCopy, isGranteeShareLoading: isGranteeShareLoading, isLoading: isGranteesLoading, isDirty: isShareDialogDirty, isLockedNow: isLockedNow, isUnderLenientControlNow: isUnderLenientControlNow, sharedObject: sharedObject, grantees: sharedGrantees, onCancel: handleCancel, onSubmit: onSubmitShareGrantee, onAddGranteeButtonClick: onAddGranteeButtonClick, onGranteeDelete: onSharedGranteeDelete, onLockChange: onLockChange, onUnderLenientControlChange: onUnderLenientControlChange, onGranularGranteeChange: onGranularGranteeShareChange, isCurrentUserWorkspaceManager: isCurrentUserWorkspaceManager })) : (_jsx(AddGranteeBase, { currentUserPermissions: currentUserPermissions, isDirty: isAddDialogDirty, currentUser: currentUser, appliedGrantees: appliedGranteesWithOwner, addedGrantees: granteesToAdd, sharedObject: sharedObject, previouslyFocusedRef: previouslyFocusedRef, onAddUserOrGroups: onGranteeAdd, onDelete: onAddedGranteeDelete, onCancel: handleCancel, onSubmit: onSubmitAddGrantee, onBackClick: onAddGranteeBackClick, onGranularGranteeChange: onGranularGranteeAddChange, isGranteeShareLoading: isGranteeShareLoading })) }) }));
25
+ return (_jsx(Overlay, { alignPoints: alignPoints, isModal: true, positionType: "fixed", className: "gd-share-dialog-overlay", children: _jsx("div", { className: "s-gd-share-dialog", children: dialogMode === "ShareGrantee" ? (_jsx(ShareGranteeBase, { currentUserPermissions: currentUserPermissions, dashboardFilters: dashboardFilters, dashboardParameters: dashboardParameters, isShareGrantHidden: isShareGrantHidden, applyShareGrantOnSelect: applyShareGrantOnSelect, showDashboardShareLink: showDashboardShareLink, onShareLinkCopy: onShareLinkCopy, isGranteeShareLoading: isGranteeShareLoading, isLoading: isGranteesLoading, isDirty: isShareDialogDirty, isLockedNow: isLockedNow, isUnderLenientControlNow: isUnderLenientControlNow, sharedObject: sharedObject, grantees: sharedGrantees, onCancel: handleCancel, onSubmit: onSubmitShareGrantee, onAddGranteeButtonClick: onAddGranteeButtonClick, onGranteeDelete: onSharedGranteeDelete, onLockChange: onLockChange, onUnderLenientControlChange: onUnderLenientControlChange, onGranularGranteeChange: onGranularGranteeShareChange, isCurrentUserWorkspaceManager: isCurrentUserWorkspaceManager })) : (_jsx(AddGranteeBase, { currentUserPermissions: currentUserPermissions, isDirty: isAddDialogDirty, currentUser: currentUser, appliedGrantees: appliedGranteesWithOwner, addedGrantees: granteesToAdd, sharedObject: sharedObject, previouslyFocusedRef: previouslyFocusedRef, onAddUserOrGroups: onGranteeAdd, onDelete: onAddedGranteeDelete, onCancel: handleCancel, onSubmit: onSubmitAddGrantee, onBackClick: onAddGranteeBackClick, onGranularGranteeChange: onGranularGranteeAddChange, isGranteeShareLoading: isGranteeShareLoading })) }) }));
26
26
  }
@@ -2,4 +2,4 @@ import { type IShareGranteeBaseProps } from "./types.js";
2
2
  /**
3
3
  * @internal
4
4
  */
5
- export declare function ShareGranteeBase({ isLoading, isLockedNow, isUnderLenientControlNow, grantees, sharedObject, isDirty, currentUserPermissions, dashboardFilters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, isGranteeShareLoading, onCancel, onSubmit, onGranteeDelete, onGranularGranteeChange, onAddGranteeButtonClick, onLockChange, onUnderLenientControlChange, isCurrentUserWorkspaceManager, onShareLinkCopy }: IShareGranteeBaseProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function ShareGranteeBase({ isLoading, isLockedNow, isUnderLenientControlNow, grantees, sharedObject, isDirty, currentUserPermissions, dashboardFilters, dashboardParameters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, isGranteeShareLoading, onCancel, onSubmit, onGranteeDelete, onGranularGranteeChange, onAddGranteeButtonClick, onLockChange, onUnderLenientControlChange, isCurrentUserWorkspaceManager, onShareLinkCopy }: IShareGranteeBaseProps): import("react/jsx-runtime").JSX.Element;
@@ -14,7 +14,7 @@ import { ADD_GRANTEE_ID } from "./utils.js";
14
14
  /**
15
15
  * @internal
16
16
  */
17
- export function ShareGranteeBase({ isLoading, isLockedNow, isUnderLenientControlNow, grantees, sharedObject, isDirty, currentUserPermissions, dashboardFilters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, isGranteeShareLoading, onCancel, onSubmit, onGranteeDelete, onGranularGranteeChange, onAddGranteeButtonClick, onLockChange, onUnderLenientControlChange, isCurrentUserWorkspaceManager, onShareLinkCopy, }) {
17
+ export function ShareGranteeBase({ isLoading, isLockedNow, isUnderLenientControlNow, grantees, sharedObject, isDirty, currentUserPermissions, dashboardFilters, dashboardParameters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, isGranteeShareLoading, onCancel, onSubmit, onGranteeDelete, onGranularGranteeChange, onAddGranteeButtonClick, onLockChange, onUnderLenientControlChange, isCurrentUserWorkspaceManager, onShareLinkCopy, }) {
18
18
  const { owner, isLeniencyControlSupported, isLockingSupported, areGranularPermissionsSupported, isMetadataObjectLockingSupported, isLocked, canWorkspaceManagerSeeEverySharedObject, } = sharedObject;
19
19
  const intl = useIntl();
20
20
  const granteeList = useMemo(() => {
@@ -42,5 +42,5 @@ export function ShareGranteeBase({ isLoading, isLockedNow, isUnderLenientControl
42
42
  }, [intl, showDashboardShareLink, applyShareGrantOnSelect, isUsingTabs]);
43
43
  const shouldDisplayAdminMessage = useMemo(() => canWorkspaceManagerSeeEverySharedObject && isCurrentUserWorkspaceManager && !isLoading, [canWorkspaceManagerSeeEverySharedObject, isCurrentUserWorkspaceManager, isLoading]);
44
44
  return (_jsxs(ConfirmDialogBase, { className: "gd-share-dialog s-gd-share-grantees", displayCloseButton: true, isPositive: true, isSubmitDisabled: !isDirty, headline: dialogLabels.headline, cancelButtonText: dialogLabels.cancelButtonText, submitButtonText: dialogLabels.submitButtonText, onCancel: onCancel, onSubmit: onSubmit, hideSubmitButton: applyShareGrantOnSelect, initialFocus: ADD_GRANTEE_ID, children: [
45
- _jsx(AdminInformationMessage, { isVisible: shouldDisplayAdminMessage ?? false }), isShareGrantHidden ? null : (_jsx(ShareGranteeContent, { currentUserPermissions: currentUserPermissions, isSharedObjectLocked: isLocked, isLoading: isLoading, grantees: granteeList, areGranularPermissionsSupported: areGranularPermissionsSupported, onAddGrantee: onAddGranteeButtonClick, onDelete: onGranteeDelete, onChange: onGranularGranteeChange, isGranteeShareLoading: isGranteeShareLoading, applyShareGrantOnSelect: !!applyShareGrantOnSelect, headline: dialogLabels.shareGrantHeadline })), showDashboardShareLink ? (_jsx(ShareLink, { dashboardFilters: dashboardFilters, onShareLinkCopy: onShareLinkCopy, headline: dialogLabels.linkHeadline, helperText: dialogLabels.linkHelperText, buttonLabel: dialogLabels.linkButtonLabel })) : null, _jsx(ContentDivider, { className: "gd-share-dialog-content-divider" }), _jsx(SharedObjectUnderLenientControl, { isUnderLenientControl: isUnderLenientControlNow, isLeniencyControlSupported: isLeniencyControlSupported, onUnderLenientControlChange: onUnderLenientControlChange }), isMetadataObjectLockingSupported ? (_jsx(SharedObjectLockControl, { isLocked: isLockedNow, isLockingSupported: isLockingSupported, onLockChange: onLockChange })) : null] }));
45
+ _jsx(AdminInformationMessage, { isVisible: shouldDisplayAdminMessage ?? false }), isShareGrantHidden ? null : (_jsx(ShareGranteeContent, { currentUserPermissions: currentUserPermissions, isSharedObjectLocked: isLocked, isLoading: isLoading, grantees: granteeList, areGranularPermissionsSupported: areGranularPermissionsSupported, onAddGrantee: onAddGranteeButtonClick, onDelete: onGranteeDelete, onChange: onGranularGranteeChange, isGranteeShareLoading: isGranteeShareLoading, applyShareGrantOnSelect: !!applyShareGrantOnSelect, headline: dialogLabels.shareGrantHeadline })), showDashboardShareLink ? (_jsx(ShareLink, { dashboardFilters: dashboardFilters, dashboardParameters: dashboardParameters, onShareLinkCopy: onShareLinkCopy, headline: dialogLabels.linkHeadline, helperText: dialogLabels.linkHelperText, buttonLabel: dialogLabels.linkButtonLabel })) : null, _jsx(ContentDivider, { className: "gd-share-dialog-content-divider" }), _jsx(SharedObjectUnderLenientControl, { isUnderLenientControl: isUnderLenientControlNow, isLeniencyControlSupported: isLeniencyControlSupported, onUnderLenientControlChange: onUnderLenientControlChange }), isMetadataObjectLockingSupported ? (_jsx(SharedObjectLockControl, { isLocked: isLockedNow, isLockingSupported: isLockingSupported, onLockChange: onLockChange })) : null] }));
46
46
  }
@@ -2,4 +2,4 @@ import { type IShareLinkProps } from "./types.js";
2
2
  /**
3
3
  * @internal
4
4
  */
5
- export declare function ShareLink({ dashboardFilters, headline, helperText, buttonLabel, onShareLinkCopy }: IShareLinkProps): import("react/jsx-runtime").JSX.Element;
5
+ export declare function ShareLink({ dashboardFilters, dashboardParameters, headline, helperText, buttonLabel, onShareLinkCopy }: IShareLinkProps): import("react/jsx-runtime").JSX.Element;
@@ -9,16 +9,21 @@ import { SHARE_LINK_HEADLINE_ID, SHARE_LINK_HELPER_TEXT_ID } from "./utils.js";
9
9
  /**
10
10
  * @internal
11
11
  */
12
- export function ShareLink({ dashboardFilters, headline, helperText, buttonLabel, onShareLinkCopy, }) {
12
+ export function ShareLink({ dashboardFilters, dashboardParameters, headline, helperText, buttonLabel, onShareLinkCopy, }) {
13
13
  const shareLink = useMemo(() => {
14
- const filters = { filters: compressForUrl(dashboardFilters ?? []) };
14
+ const queryParams = {
15
+ filters: compressForUrl(dashboardFilters ?? []),
16
+ };
17
+ if (dashboardParameters?.length) {
18
+ queryParams["parameters"] = compressForUrl(dashboardParameters);
19
+ }
15
20
  const url = window.location.origin;
16
21
  const hashLocation = window.location.hash.split("?")[0];
17
22
  return url
18
23
  .concat("/dashboards/")
19
24
  .concat(hashLocation)
20
- .concat(`?${new URLSearchParams(filters).toString()}`);
21
- }, [dashboardFilters]);
25
+ .concat(`?${new URLSearchParams(queryParams).toString()}`);
26
+ }, [dashboardFilters, dashboardParameters]);
22
27
  const onIconButtonClick = useCallback(() => {
23
28
  onShareLinkCopy?.(shareLink);
24
29
  }, [shareLink, onShareLinkCopy]);
@@ -1,5 +1,5 @@
1
1
  import { type MutableRefObject, type ReactNode } from "react";
2
- import { type AccessGranularPermission, type FilterContextItem, type IUser, type ObjRef, type ShareStatus } from "@gooddata/sdk-model";
2
+ import { type AccessGranularPermission, type FilterContextItem, type IDashboardParameter, type IUser, type ObjRef, type ShareStatus } from "@gooddata/sdk-model";
3
3
  import { type CurrentUserPermissions, type IShareDialogLabels } from "../types.js";
4
4
  /**
5
5
  * @internal
@@ -159,6 +159,7 @@ export interface IShareDialogBaseProps {
159
159
  currentUser: IUser;
160
160
  currentUserPermissions: CurrentUserPermissions;
161
161
  dashboardFilters?: FilterContextItem[];
162
+ dashboardParameters?: IDashboardParameter[];
162
163
  isShareGrantHidden?: boolean;
163
164
  applyShareGrantOnSelect?: boolean;
164
165
  showDashboardShareLink?: boolean;
@@ -195,6 +196,7 @@ export interface IShareGranteeBaseProps {
195
196
  isCurrentUserWorkspaceManager: boolean;
196
197
  currentUserPermissions: CurrentUserPermissions;
197
198
  dashboardFilters?: FilterContextItem[];
199
+ dashboardParameters?: IDashboardParameter[];
198
200
  isShareGrantHidden?: boolean;
199
201
  applyShareGrantOnSelect?: boolean;
200
202
  showDashboardShareLink?: boolean;
@@ -277,6 +279,7 @@ export interface IGranteesListProps {
277
279
  */
278
280
  export interface IShareLinkProps {
279
281
  dashboardFilters?: FilterContextItem[];
282
+ dashboardParameters?: IDashboardParameter[];
280
283
  headline: string;
281
284
  helperText: string;
282
285
  buttonLabel: string;
@@ -1,4 +1,4 @@
1
- // (C) 2021-2025 GoodData Corporation
1
+ // (C) 2021-2026 GoodData Corporation
2
2
  import { isEmpty } from "lodash-es";
3
3
  /**
4
4
  * @internal
@@ -1,5 +1,5 @@
1
1
  import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi";
2
- import { type AccessGranularPermission, type FilterContextItem, type IAccessControlAware, type IAccessGrantee, type IAuditableUsers, type IUser, type ObjRef, type ShareStatus } from "@gooddata/sdk-model";
2
+ import { type AccessGranularPermission, type FilterContextItem, type IAccessControlAware, type IAccessGrantee, type IAuditableUsers, type IDashboardParameter, type IUser, type ObjRef, type ShareStatus } from "@gooddata/sdk-model";
3
3
  import { type GoodDataSdkError } from "@gooddata/sdk-ui";
4
4
  /**
5
5
  * @internal
@@ -50,6 +50,7 @@ export interface IShareDialogProps {
50
50
  labels: IShareDialogLabels;
51
51
  currentUserPermissions: CurrentUserPermissions;
52
52
  dashboardFilters?: FilterContextItem[];
53
+ dashboardParameters?: IDashboardParameter[];
53
54
  isGranteeShareLoading?: boolean;
54
55
  isShareGrantHidden?: boolean;
55
56
  applyShareGrantOnSelect?: boolean;
@@ -1 +1,2 @@
1
+ // (C) 2021-2026 GoodData Corporation
1
2
  export {};
@@ -82,13 +82,21 @@ export function DropdownList({ id, title, className = "", tabsClassName = "", wi
82
82
  ? Math.max(mobileItemHeight, itemHeight)
83
83
  : itemHeight;
84
84
  const effectiveMaxHeight = maxHeight || listHeight || 300;
85
- return (_jsx("div", { "data-testid": "gd-dropdown-list", id: id, style: { width: listWidth }, className: listClassNames, role: accessibilityConfig?.role === "listbox"
85
+ return (_jsx("div", { "data-testid": "gd-dropdown-list", id: id, style: { width: listWidth }, className: listClassNames, role: accessibilityConfig?.role === "listbox" ||
86
+ accessibilityConfig?.role === "list"
86
87
  ? undefined
87
- : accessibilityConfig?.role, "aria-label": accessibilityConfig?.role === "listbox"
88
+ : accessibilityConfig?.role, "aria-label": accessibilityConfig?.role === "listbox" ||
89
+ accessibilityConfig?.role === "list"
88
90
  ? undefined
89
- : accessibilityConfig?.ariaLabel, "aria-labelledby": accessibilityConfig?.role === "listbox"
91
+ : accessibilityConfig?.ariaLabel, "aria-labelledby": accessibilityConfig?.role === "listbox" ||
92
+ accessibilityConfig?.role === "list"
90
93
  ? undefined
91
- : accessibilityConfig?.ariaLabelledBy, children: _jsx(UiPagedVirtualList, { maxHeight: effectiveMaxHeight, items: items, itemHeight: effectiveItemHeight, itemsGap: 0, itemPadding: 0, containerPadding: containerPadding, onKeyDownSelect: onKeyDownSelect, onKeyDownConfirm: onKeyDownConfirm, closeDropdown: closeDropdown, itemHeightGetter: itemHeightGetter, hasNextPage: hasNextPage, loadNextPage: loadNextPage, skeletonItemsCount: skeletonItemsCount, SkeletonItem: SkeletonItem, shouldLoadNextPage: shouldLoadNextPage, isLoading: isNextPageLoading, onScroll: onScroll, scrollToItem: scrollToItem, scrollToItemKeyExtractor: scrollToItemKeyExtractor, representAs: accessibilityConfig?.role === "listbox" ? "listbox" : undefined, listboxProps: accessibilityConfig?.role === "listbox"
94
+ : accessibilityConfig?.ariaLabelledBy, children: _jsx(UiPagedVirtualList, { maxHeight: effectiveMaxHeight, items: items, itemHeight: effectiveItemHeight, itemsGap: 0, itemPadding: 0, containerPadding: containerPadding, onKeyDownSelect: onKeyDownSelect, onKeyDownConfirm: onKeyDownConfirm, closeDropdown: closeDropdown, itemHeightGetter: itemHeightGetter, hasNextPage: hasNextPage, loadNextPage: loadNextPage, skeletonItemsCount: skeletonItemsCount, SkeletonItem: SkeletonItem, shouldLoadNextPage: shouldLoadNextPage, isLoading: isNextPageLoading, onScroll: onScroll, scrollToItem: scrollToItem, scrollToItemKeyExtractor: scrollToItemKeyExtractor, representAs: accessibilityConfig?.role === "listbox"
95
+ ? "listbox"
96
+ : accessibilityConfig?.role === "list"
97
+ ? "list"
98
+ : undefined, listboxProps: accessibilityConfig?.role === "listbox" ||
99
+ accessibilityConfig?.role === "list"
92
100
  ? {
93
101
  "aria-label": accessibilityConfig?.ariaLabel,
94
102
  "aria-labelledby": accessibilityConfig?.ariaLabelledBy,
@@ -27,11 +27,18 @@ export function FilterGroupItem({ title, subtitle, selectedItemsCount, totalItem
27
27
  }
28
28
  const tooltipId = useIdPrefixed("filter-group-item-locked-tooltip");
29
29
  const onKeyDown = useCallback((event) => {
30
- if (disabled && isActionKey(event)) {
31
- event.preventDefault();
32
- event.stopPropagation();
30
+ // <div role="button"> doesn't reliably fire click on Enter/Space across browsers and
31
+ // AT modes (ARIA APG requires manual handling). Dispatch onClick ourselves.
32
+ if (!isActionKey(event)) {
33
+ return;
34
+ }
35
+ event.preventDefault();
36
+ event.stopPropagation();
37
+ if (disabled) {
38
+ return;
33
39
  }
34
- }, [disabled]);
40
+ onClick?.();
41
+ }, [disabled, onClick]);
35
42
  const handleButtonRef = useCallback((element) => {
36
43
  if (!buttonRef) {
37
44
  return;
@@ -57,7 +64,7 @@ export function FilterGroupItem({ title, subtitle, selectedItemsCount, totalItem
57
64
  "gd-is-active": isOpen,
58
65
  "gd-is-loaded": isLoaded,
59
66
  "gd-is-disabled": disabled,
60
- }), "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-describedby": disabled ? tooltipId : undefined, "aria-disabled": disabled, onClick: disabled ? undefined : onClick, onKeyDown: onKeyDown, "aria-controls": isOpen ? dropdownId : undefined, role: "button", tabIndex: -1, ref: handleButtonRef, "data-testid": `s-filter-group-item-${simplifyText(title ?? null)}`, children: [isError || icon ? (_jsx("div", { className: "gd-filter-group-item-icon", children: isError ? _jsx(UiIcon, { type: "crossCircle", size: 12, color: "currentColor" }) : icon })) : null, _jsxs("div", { className: "gd-filter-group-item-body", children: [
67
+ }), "aria-haspopup": "dialog", "aria-expanded": isOpen, "aria-describedby": disabled ? tooltipId : undefined, "aria-disabled": disabled, onClick: disabled ? undefined : onClick, onKeyDown: onKeyDown, "aria-controls": isOpen ? dropdownId : undefined, role: "button", tabIndex: 0, ref: handleButtonRef, "data-testid": `s-filter-group-item-${simplifyText(title ?? null)}`, children: [isError || icon ? (_jsx("div", { className: "gd-filter-group-item-icon", children: isError ? _jsx(UiIcon, { type: "crossCircle", size: 12, color: "currentColor" }) : icon })) : null, _jsxs("div", { className: "gd-filter-group-item-body", children: [
61
68
  _jsxs("div", { className: "gd-filter-group-item-content", children: [
62
69
  _jsxs("div", { className: "gd-filter-group-item-title-content", children: [
63
70
  _jsx("div", { className: "gd-filter-group-item-title", children: _jsx(ShortenedText, { tooltipAlignPoints: ALIGN_POINT, "data-testid": "s-filter-group-item-title", ellipsisPosition: "end", children: `${buttonTitle}` }) }), _jsx("div", { className: "gd-filter-group-item-title-extension", children: titleExtension })
@@ -71,7 +71,7 @@ function createInsightsItemsGroup(featureFlags, workspaceId, workspacePermission
71
71
  pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_METRICS, "s-menu-metrics", measuresUrl), canShowMetricsItem(hasMeasures, workspacePermissions));
72
72
  const dataUrl = dataItemUrl(workspaceId, workspacePermissions, backendSupportsDataItem, hasNoDataSet, baseUrl);
73
73
  pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_DATA, "s-menu-data", dataUrl), canShowDataItem(featureFlags, workspacePermissions));
74
- pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_CATALOG, "s-menu-workspace-catalog", catalogItemUrl(workspaceId), true), canShowCatalogItem(featureFlags, workspacePermissions));
74
+ pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_CATALOG, "s-menu-workspace-catalog", catalogItemUrl(workspaceId, featureFlags), true), canShowCatalogItem(featureFlags, workspacePermissions));
75
75
  const loadUrl = loadItemUrl(baseUrl, workspaceId);
76
76
  pushConditionally(insightItemsGroup, createIHeaderMenuItem(HEADER_ITEM_ID_LOAD, "s-menu-load", loadUrl), canShowLoadItem(featureFlags, workspacePermissions, isFreemiumCustomer, backendSupportsCsvUploader));
77
77
  return insightItemsGroup;
@@ -90,7 +90,15 @@ function pushConditionally(items, item, cond) {
90
90
  }
91
91
  }
92
92
  const withBaseUrl = (baseUrl, uri) => `${baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length - 2) : baseUrl}${uri}`;
93
- function catalogItemUrl(workspaceId) {
93
+ function catalogItemUrl(workspaceId, featureFlags) {
94
+ // When the host app and the catalog pluggable-app migration are both on,
95
+ // link directly to the singular host route. Otherwise fall back to the
96
+ // legacy plural URL owned by gdc-home-ui, which re-redirects to the host
97
+ // route on arrival. Skipping the indirection avoids a double redirect when
98
+ // switching from Dashboards/Analyze/Metrics/Data to Catalog. See LX-2426.
99
+ if (featureFlags.enableShellApplication && featureFlags.enableShellApplication_catalog) {
100
+ return `/workspace/${workspaceId}/catalog`;
101
+ }
94
102
  return `/workspaces/${workspaceId}/catalog`;
95
103
  }
96
104
  function canShowCatalogItem(featureFlags, workspacePermissions) {
package/esm/Icon/Icon.js CHANGED
@@ -1,4 +1,4 @@
1
- // (C) 2021-2025 GoodData Corporation
1
+ // (C) 2021-2026 GoodData Corporation
2
2
  import { Aborted } from "./icons/Aborted.js";
3
3
  import { Alert } from "./icons/Alert.js";
4
4
  import { AlertPaused } from "./icons/AlertPaused.js";
@@ -59,6 +59,7 @@ import { HeatMap } from "./icons/InsightIcons/HeatMap.js";
59
59
  import { Line } from "./icons/InsightIcons/Line.js";
60
60
  import { Pie } from "./icons/InsightIcons/Pie.js";
61
61
  import { Pyramid } from "./icons/InsightIcons/Pyramid.js";
62
+ import { Radar } from "./icons/InsightIcons/Radar.js";
62
63
  import { Repeater } from "./icons/InsightIcons/Repeater.js";
63
64
  import { Sankey } from "./icons/InsightIcons/Sankey.js";
64
65
  import { ScatterPlot } from "./icons/InsightIcons/ScatterPlot.js";
@@ -185,6 +186,7 @@ export const Icon = {
185
186
  Run,
186
187
  Invite,
187
188
  Pyramid,
189
+ Radar,
188
190
  Funnel,
189
191
  RichText,
190
192
  VisualizationSwitcher,
@@ -1,4 +1,4 @@
1
- // (C) 2022-2025 GoodData Corporation
1
+ // (C) 2022-2026 GoodData Corporation
2
2
  import { Icon } from "./Icon.js";
3
3
  const INSIGHT_ICON_MAP = {
4
4
  "local:scatter": Icon["ScatterPlot"],
@@ -20,6 +20,7 @@ const INSIGHT_ICON_MAP = {
20
20
  "local:dependencywheel": Icon["DependencyWheel"],
21
21
  "local:funnel": Icon["Funnel"],
22
22
  "local:pyramid": Icon["Pyramid"],
23
+ "local:radar": Icon["Radar"],
23
24
  "local:waterfall": Icon["Waterfall"],
24
25
  "local:repeater": Icon["Repeater"],
25
26
  };
@@ -0,0 +1,5 @@
1
+ import { type IIconProps } from "../../typings.js";
2
+ /**
3
+ * @internal
4
+ */
5
+ export declare function Radar({ className, width, height, color, ariaHidden }: IIconProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { combineIconClasses } from "../../utils.js";
3
+ /**
4
+ * @internal
5
+ */
6
+ export function Radar({ className, width, height, color, ariaHidden }) {
7
+ return (_jsxs("svg", { width: width ?? 32, height: height ?? 30, className: combineIconClasses(className), viewBox: "0 0 32 30", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": ariaHidden, children: [
8
+ _jsx("path", { d: "M14.4122 15.4619L9.40442 22.4717C7.31888 20.639 6.0011 17.9536 6.0011 14.959C6.0011 14.1517 6.0988 13.3672 6.27942 12.6152L14.4122 15.4619Z", fill: color ?? "#B0BECA", fillOpacity: "0.6" }), _jsx("path", { d: "M17.0011 4.00488C21.0165 4.36689 24.4069 6.887 26.0089 10.3965L17.0011 13.5488V4.00488Z", fill: color ?? "#B0BECA", fillOpacity: "0.6" }), _jsx("path", { d: "M15.0011 13.5488L5.04504 10.0645C6.78918 6.16628 10.5491 3.36728 15.0011 3V13.5488Z", fill: color ?? "#B0BECA" }), _jsx("path", { d: "M24.7734 12.9201C24.9196 13.5616 25 14.2285 25 14.9142C25 17.5737 23.8441 19.9605 22.0098 21.6076L17.5889 15.4191L24.7734 12.9201Z", fill: color ?? "#B0BECA" }), _jsx("path", { d: "M22.1359 25.2676C20.3405 26.3395 18.2441 26.959 16.0011 26.959C13.7577 26.959 11.66 26.3406 9.86438 25.2686L16.0011 16.6787L22.1359 25.2676Z", fill: color ?? "#B0BECA", fillOpacity: "0.4" })
9
+ ] }));
10
+ }
package/esm/index.d.ts CHANGED
@@ -233,6 +233,7 @@ export { HeatMap as IconHeatMap } from "./Icon/icons/InsightIcons/HeatMap.js";
233
233
  export { Line as IconLine } from "./Icon/icons/InsightIcons/Line.js";
234
234
  export { Pie as IconPie } from "./Icon/icons/InsightIcons/Pie.js";
235
235
  export { Pyramid as IconPyramid } from "./Icon/icons/InsightIcons/Pyramid.js";
236
+ export { Radar as IconRadar } from "./Icon/icons/InsightIcons/Radar.js";
236
237
  export { Repeater as IconRepeater } from "./Icon/icons/InsightIcons/Repeater.js";
237
238
  export { Sankey as IconSankey } from "./Icon/icons/InsightIcons/Sankey.js";
238
239
  export { ScatterPlot as IconScatterPlot } from "./Icon/icons/InsightIcons/ScatterPlot.js";
package/esm/index.js CHANGED
@@ -220,6 +220,7 @@ export { HeatMap as IconHeatMap } from "./Icon/icons/InsightIcons/HeatMap.js";
220
220
  export { Line as IconLine } from "./Icon/icons/InsightIcons/Line.js";
221
221
  export { Pie as IconPie } from "./Icon/icons/InsightIcons/Pie.js";
222
222
  export { Pyramid as IconPyramid } from "./Icon/icons/InsightIcons/Pyramid.js";
223
+ export { Radar as IconRadar } from "./Icon/icons/InsightIcons/Radar.js";
223
224
  export { Repeater as IconRepeater } from "./Icon/icons/InsightIcons/Repeater.js";
224
225
  export { Sankey as IconSankey } from "./Icon/icons/InsightIcons/Sankey.js";
225
226
  export { ScatterPlot as IconScatterPlot } from "./Icon/icons/InsightIcons/ScatterPlot.js";