@gooddata/sdk-ui-kit 11.42.0-alpha.0 → 11.42.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.
Files changed (76) hide show
  1. package/esm/@ui/UiCombobox/UiComboboxInput.d.ts +22 -4
  2. package/esm/@ui/UiCombobox/UiComboboxInput.d.ts.map +1 -1
  3. package/esm/@ui/UiCombobox/UiComboboxInput.js +38 -31
  4. package/esm/@ui/UiCombobox/UiComboboxList.js +2 -2
  5. package/esm/@ui/UiCombobox/UiComboboxListItem.d.ts +3 -13
  6. package/esm/@ui/UiCombobox/UiComboboxListItem.d.ts.map +1 -1
  7. package/esm/@ui/UiCombobox/UiComboboxListItem.js +13 -23
  8. package/esm/@ui/UiCombobox/UiComboboxPopup.d.ts +6 -3
  9. package/esm/@ui/UiCombobox/UiComboboxPopup.d.ts.map +1 -1
  10. package/esm/@ui/UiCombobox/UiComboboxPopup.js +5 -7
  11. package/esm/@ui/UiCombobox/types.d.ts +17 -9
  12. package/esm/@ui/UiCombobox/types.d.ts.map +1 -1
  13. package/esm/@ui/UiCombobox/useCombobox.d.ts.map +1 -1
  14. package/esm/@ui/UiCombobox/useCombobox.js +73 -161
  15. package/esm/@ui/UiCombobox/useComboboxChrome.d.ts +9 -0
  16. package/esm/@ui/UiCombobox/useComboboxChrome.d.ts.map +1 -0
  17. package/esm/@ui/UiCombobox/useComboboxChrome.js +61 -0
  18. package/esm/@ui/UiCombobox/useComboboxSelection.d.ts +24 -0
  19. package/esm/@ui/UiCombobox/useComboboxSelection.d.ts.map +1 -0
  20. package/esm/@ui/UiCombobox/useComboboxSelection.js +82 -0
  21. package/esm/@ui/UiDropdown/UiDropdown.js +3 -3
  22. package/esm/@ui/UiFloatingElement/UiFloatingElement.d.ts.map +1 -1
  23. package/esm/@ui/UiFloatingElement/UiFloatingElement.js +3 -2
  24. package/esm/@ui/UiFloatingPanel/UiFloatingPanel.d.ts +28 -0
  25. package/esm/@ui/UiFloatingPanel/UiFloatingPanel.d.ts.map +1 -0
  26. package/esm/@ui/UiFloatingPanel/UiFloatingPanel.js +14 -0
  27. package/esm/@ui/UiTags/UiTags.d.ts.map +1 -1
  28. package/esm/@ui/UiTags/UiTags.js +2 -2
  29. package/esm/@ui/UiTextInput/UiTextInput.d.ts +13 -2
  30. package/esm/@ui/UiTextInput/UiTextInput.d.ts.map +1 -1
  31. package/esm/@ui/UiTextInput/UiTextInput.js +2 -2
  32. package/esm/@ui/UiTooltip/UiTooltip.d.ts.map +1 -1
  33. package/esm/@ui/UiTooltip/UiTooltip.js +10 -4
  34. package/esm/@ui/hooks/useCloseOnOutsideClick.d.ts +31 -0
  35. package/esm/@ui/hooks/useCloseOnOutsideClick.d.ts.map +1 -1
  36. package/esm/@ui/hooks/useCloseOnOutsideClick.js +73 -6
  37. package/esm/Dialog/ShareDialog/ShareDialog.d.ts +1 -1
  38. package/esm/Dialog/ShareDialog/ShareDialog.d.ts.map +1 -1
  39. package/esm/Dialog/ShareDialog/ShareDialog.js +2 -2
  40. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareDialogBase.d.ts.map +1 -1
  41. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareDialogBase.js +2 -2
  42. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.d.ts +1 -1
  43. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.d.ts.map +1 -1
  44. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.js +2 -2
  45. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.d.ts +1 -1
  46. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.d.ts.map +1 -1
  47. package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.js +3 -3
  48. package/esm/Dialog/ShareDialog/ShareDialogBase/types.d.ts +3 -0
  49. package/esm/Dialog/ShareDialog/ShareDialogBase/types.d.ts.map +1 -1
  50. package/esm/Dialog/ShareDialog/types.d.ts +5 -0
  51. package/esm/Dialog/ShareDialog/types.d.ts.map +1 -1
  52. package/esm/Header/generateHeaderMenuItemsGroups.js +3 -3
  53. package/esm/Tabs/Tabs.d.ts +2 -1
  54. package/esm/Tabs/Tabs.d.ts.map +1 -1
  55. package/esm/Tabs/Tabs.js +1 -1
  56. package/esm/index.d.ts +3 -2
  57. package/esm/index.d.ts.map +1 -1
  58. package/esm/index.js +1 -0
  59. package/esm/sdk-ui-kit.d.ts +99 -33
  60. package/package.json +11 -11
  61. package/src/@ui/UiCombobox/UiCombobox.scss +0 -16
  62. package/src/@ui/UiDropdown/UiDropdown.scss +1 -20
  63. package/src/@ui/UiFloatingPanel/UiFloatingPanel.scss +27 -0
  64. package/src/@ui/UiMenu/UiMenu.scss +0 -1
  65. package/src/@ui/UiPopover/UiPopover.scss +8 -0
  66. package/src/@ui/UiTags/UiTags.scss +0 -4
  67. package/src/@ui/UiTextInput/UiTextInput.scss +6 -2
  68. package/src/@ui/index.scss +1 -0
  69. package/styles/css/list.css +6 -1
  70. package/styles/css/list.css.map +1 -1
  71. package/styles/css/main.css +24 -30
  72. package/styles/css/main.css.map +1 -1
  73. package/styles/css/menu.css +6 -1
  74. package/styles/css/menu.css.map +1 -1
  75. package/styles/icons/dashboard.svg +5 -0
  76. package/styles/scss/list.scss +6 -1
@@ -0,0 +1,61 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { useCallback, useMemo, useRef, useState } from "react";
3
+ /** @internal */
4
+ export function useComboboxChrome() {
5
+ const [isOpen, setIsOpen] = useState(false);
6
+ const [activeIndex, setActiveIndex] = useState(null);
7
+ const anchorRef = useRef(null);
8
+ const itemRefs = useRef([]);
9
+ // Target index for when the scroll request runs before the item mounts.
10
+ const pendingScrollIndexRef = useRef(null);
11
+ const scrollIndexIntoView = useCallback((index) => {
12
+ if (index == null) {
13
+ pendingScrollIndexRef.current = null;
14
+ return;
15
+ }
16
+ const node = itemRefs.current[index];
17
+ if (node) {
18
+ pendingScrollIndexRef.current = null;
19
+ node.scrollIntoView({ block: "nearest" });
20
+ }
21
+ else {
22
+ pendingScrollIndexRef.current = index;
23
+ }
24
+ }, []);
25
+ const registerItemRef = useCallback((node, index) => {
26
+ itemRefs.current[index] = node;
27
+ if (node != null && pendingScrollIndexRef.current === index) {
28
+ pendingScrollIndexRef.current = null;
29
+ node.scrollIntoView({ block: "nearest" });
30
+ }
31
+ }, []);
32
+ const focusByDelta = useCallback((delta, total) => {
33
+ if (total === 0) {
34
+ setIsOpen(true);
35
+ return;
36
+ }
37
+ // When opening from a closed state, jump to the first / last item.
38
+ if (!isOpen) {
39
+ setIsOpen(true);
40
+ const next = delta === 1 ? 0 : total - 1;
41
+ setActiveIndex(next);
42
+ scrollIndexIntoView(next);
43
+ return;
44
+ }
45
+ // Clamp on read so a stale activeIndex from a shrunken list still
46
+ // produces a sensible wrap.
47
+ const current = activeIndex == null ? null : Math.min(activeIndex, total - 1);
48
+ const next = current == null ? (delta === 1 ? 0 : total - 1) : (current + delta + total) % total;
49
+ setActiveIndex(next);
50
+ scrollIndexIntoView(next);
51
+ }, [isOpen, activeIndex, scrollIndexIntoView]);
52
+ return useMemo(() => ({
53
+ isOpen,
54
+ setIsOpen,
55
+ activeIndex,
56
+ setActiveIndex,
57
+ anchorRef,
58
+ registerItemRef,
59
+ focusByDelta,
60
+ }), [isOpen, activeIndex, anchorRef, registerItemRef, focusByDelta]);
61
+ }
@@ -0,0 +1,24 @@
1
+ import type { IUiComboboxOption } from "./types.js";
2
+ /** @internal */
3
+ export interface IUseComboboxSelectionParams {
4
+ options: IUiComboboxOption[];
5
+ value?: string;
6
+ defaultValue?: string;
7
+ onValueChange?: (value: string) => void;
8
+ creatable?: boolean;
9
+ setIsOpen: (open: boolean) => void;
10
+ setActiveIndex: (index: number | null) => void;
11
+ }
12
+ /**
13
+ * @internal
14
+ */
15
+ export declare function useComboboxSelection({ options, value, defaultValue, onValueChange, creatable, setIsOpen, setActiveIndex }: IUseComboboxSelectionParams): {
16
+ availableOptions: IUiComboboxOption[];
17
+ inputValue: string;
18
+ onInputChange: (next: string) => void;
19
+ onInputBlur: () => void;
20
+ selectedOption: IUiComboboxOption | undefined;
21
+ selectOption: (option: IUiComboboxOption, index?: number | undefined) => void;
22
+ resetState: () => void;
23
+ };
24
+ //# sourceMappingURL=useComboboxSelection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useComboboxSelection.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiCombobox/useComboboxSelection.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAGpD,gBAAgB;AAChB,MAAM,WAAW,2BAA2B;IACxC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAClD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,OAAO,EACP,KAAK,EACL,YAAiB,EACjB,aAAa,EACb,SAAiB,EACjB,SAAS,EACT,cAAc,EACjB,EAAE,2BAA2B;;;;;;;;EAgF7B"}
@@ -0,0 +1,82 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { useCallback, useMemo, useState } from "react";
3
+ import { normalizeValue } from "./utils.js";
4
+ /**
5
+ * @internal
6
+ */
7
+ export function useComboboxSelection({ options, value, defaultValue = "", onValueChange, creatable = false, setIsOpen, setActiveIndex, }) {
8
+ const [inputValue, setInputValue] = useControlledValue({ value, defaultValue, onValueChange });
9
+ const [selectedOption, setSelectedOption] = useState(undefined);
10
+ const resetState = useCallback(() => {
11
+ setInputValue(defaultValue);
12
+ setActiveIndex(null);
13
+ setSelectedOption(undefined);
14
+ }, [defaultValue, setInputValue, setActiveIndex]);
15
+ const availableOptions = useMemo(() => {
16
+ const value = normalizeValue(inputValue);
17
+ const selectedValue = selectedOption ? normalizeValue(selectedOption.label) : undefined;
18
+ // Show all options when there is no value or the value is the same as the selected value
19
+ if (!value || value === selectedValue) {
20
+ return options;
21
+ }
22
+ const matchedOptions = options.filter((option) => normalizeValue(option.label).includes(value));
23
+ const hasExactMatch = matchedOptions.some((option) => normalizeValue(option.label) === value);
24
+ // Add a creatable option if there are multiple matches and none matches the input exactly
25
+ if (creatable && !hasExactMatch) {
26
+ matchedOptions.push({
27
+ id: `creatable/${inputValue}`,
28
+ label: inputValue,
29
+ creatable: true,
30
+ });
31
+ }
32
+ return matchedOptions;
33
+ }, [options, inputValue, selectedOption, creatable]);
34
+ const selectOption = useCallback((option, index) => {
35
+ // Hover sets activeIndex without checking `disabled`, so without
36
+ // this guard a disabled row could be Enter-confirmed even though
37
+ // clicking it is blocked in the list item.
38
+ if (option.disabled) {
39
+ return;
40
+ }
41
+ if (index !== undefined) {
42
+ setActiveIndex(index);
43
+ }
44
+ setSelectedOption(option);
45
+ setIsOpen(false);
46
+ setInputValue(option.label);
47
+ }, [setActiveIndex, setIsOpen, setInputValue]);
48
+ const onInputChange = useCallback((next) => {
49
+ setInputValue(next);
50
+ setIsOpen(true);
51
+ }, [setInputValue, setIsOpen]);
52
+ const onInputBlur = useCallback(() => {
53
+ if (inputValue && selectedOption && inputValue !== selectedOption.label && !creatable) {
54
+ setInputValue(selectedOption.label);
55
+ }
56
+ setIsOpen(false);
57
+ }, [inputValue, selectedOption, setInputValue, creatable, setIsOpen]);
58
+ return useMemo(() => ({
59
+ availableOptions,
60
+ inputValue,
61
+ onInputChange,
62
+ onInputBlur,
63
+ selectedOption,
64
+ selectOption,
65
+ resetState,
66
+ }), [availableOptions, inputValue, onInputChange, onInputBlur, selectedOption, selectOption, resetState]);
67
+ }
68
+ function useControlledValue(params) {
69
+ const { value, defaultValue, onValueChange } = params;
70
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
71
+ const isControlled = value !== undefined;
72
+ const controlledValue = isControlled ? value : uncontrolledValue;
73
+ const setControlledValue = useCallback((newValue) => {
74
+ if (isControlled) {
75
+ onValueChange?.(newValue);
76
+ }
77
+ else {
78
+ setUncontrolledValue(newValue);
79
+ }
80
+ }, [isControlled, onValueChange]);
81
+ return [controlledValue, setControlledValue];
82
+ }
@@ -5,7 +5,7 @@ import { usePropState } from "@gooddata/sdk-ui";
5
5
  import { isElementTextInput } from "../../utils/domUtilities.js";
6
6
  import { useId } from "../../utils/useId.js";
7
7
  import { bem } from "../@utils/bem.js";
8
- import { UiFloatingElement } from "../UiFloatingElement/UiFloatingElement.js";
8
+ import { UiFloatingPanel } from "../UiFloatingPanel/UiFloatingPanel.js";
9
9
  import { UiFocusManager } from "../UiFocusManager/UiFocusManager.js";
10
10
  import { resolveRef } from "../UiFocusManager/utils.js";
11
11
  const { e } = bem("gd-ui-kit-dropdown");
@@ -118,9 +118,9 @@ export function UiDropdown({ renderButton, renderBody, isOpen: isOpenProp, onOpe
118
118
  },
119
119
  }), [closeDropdown, dropdownId]);
120
120
  return (_jsxs(_Fragment, { children: [
121
- _jsx("div", { ref: buttonWrapperRef, id: buttonId, className: e("button", { fullWidth: !!fullWidthButton }), onKeyDown: handleKeyDown, children: renderButton(buttonRenderProps) }), _jsx(UiFloatingElement, { anchor: buttonWrapperRef, isOpen: isOpen, onClose: closeDropdown, placement: placement, offset: offset, alignPoints: alignPoints, closeOnOutsideClick: closeOnOutsideClick, closeOnEscape: closeOnEscape, closeOnParentScroll: closeOnParentScroll, closeOnMouseDrag: closeOnMouseDrag, ignoreClicksOn: ignoreClicksOnByClass, zIndex: zIndex, width: width, accessibilityConfig: {
121
+ _jsx("div", { ref: buttonWrapperRef, id: buttonId, className: e("button", { fullWidth: !!fullWidthButton }), onKeyDown: handleKeyDown, children: renderButton(buttonRenderProps) }), _jsx(UiFloatingPanel, { anchor: buttonWrapperRef, isOpen: isOpen, onClose: closeDropdown, placement: placement, offset: offset, alignPoints: alignPoints, closeOnOutsideClick: closeOnOutsideClick, closeOnEscape: closeOnEscape, closeOnParentScroll: closeOnParentScroll, closeOnMouseDrag: closeOnMouseDrag, ignoreClicksOn: ignoreClicksOnByClass, zIndex: zIndex, width: width, accessibilityConfig: {
122
122
  role: accessibilityConfig?.popupRole ?? "dialog",
123
123
  ariaLabelledBy: buttonId,
124
- }, children: _jsx(UiFocusManager, { tabOutHandler: enableFocusTrap ? undefined : handleTabOut, enableFocusTrap: enableFocusTrap, enableAutofocus: autofocusOnOpen ? { initialFocus } : false, enableReturnFocusOnUnmount: { returnFocusTo: returnFocusTo ?? buttonRef }, children: _jsx("div", { className: e("body"), children: renderBody(bodyRenderProps) }) }) })
124
+ }, children: _jsx(UiFocusManager, { tabOutHandler: enableFocusTrap ? undefined : handleTabOut, enableFocusTrap: enableFocusTrap, enableAutofocus: autofocusOnOpen ? { initialFocus } : false, enableReturnFocusOnUnmount: { returnFocusTo: returnFocusTo ?? buttonRef }, children: renderBody(bodyRenderProps) }) })
125
125
  ] }));
126
126
  }
@@ -1 +1 @@
1
- {"version":3,"file":"UiFloatingElement.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiFloatingElement/UiFloatingElement.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAS1D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,SAA0B,EAC1B,WAAW,EACX,QAAqB,EACrB,MAAM,EACN,QAAe,EACf,mBAA0B,EAC1B,aAAqB,EACrB,mBAA2B,EAC3B,gBAAwB,EACxB,cAAuC,EACvC,kBAAkB,EAClB,MAAM,EAAE,UAAU,EAClB,SAAS,EACT,gBAAgB,EAChB,KAAK,EACL,KAAc,EACd,QAAQ,EACR,SAAS,EACT,mBAAmB,EACnB,iBAAiB,EACpB,EAAE,uBAAuB,kDA0FzB"}
1
+ {"version":3,"file":"UiFloatingElement.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiFloatingElement/UiFloatingElement.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAS1D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,SAA0B,EAC1B,WAAW,EACX,QAAqB,EACrB,MAAM,EACN,QAAe,EACf,mBAA0B,EAC1B,aAAqB,EACrB,mBAA2B,EAC3B,gBAAwB,EACxB,cAAuC,EACvC,kBAAkB,EAClB,MAAM,EAAE,UAAU,EAClB,SAAS,EACT,gBAAgB,EAChB,KAAK,EACL,KAAc,EACd,QAAQ,EACR,SAAS,EACT,mBAAmB,EACnB,iBAAiB,EACpB,EAAE,uBAAuB,kDA+FzB"}
@@ -7,7 +7,7 @@ import { ConditionalScopedThemeProvider } from "@gooddata/sdk-ui-theme-provider"
7
7
  import { bem } from "../@utils/bem.js";
8
8
  import { useCloseOnEscape } from "../hooks/useCloseOnEscape.js";
9
9
  import { useCloseOnMouseDrag } from "../hooks/useCloseOnMouseDrag.js";
10
- import { FLOATING_ELEMENT_DATA_ATTR, useCloseOnOutsideClick } from "../hooks/useCloseOnOutsideClick.js";
10
+ import { FLOATING_ELEMENT_DATA_ATTR, useCloseOnOutsideClick, useRegisterFloatingAnchor, } from "../hooks/useCloseOnOutsideClick.js";
11
11
  import { useCloseOnParentScroll } from "../hooks/useCloseOnParentScroll.js";
12
12
  import { useFloatingPosition } from "./useFloatingPosition.js";
13
13
  import { getDimensionsFromRef, resolveAnchor } from "./utils.js";
@@ -79,10 +79,11 @@ export function UiFloatingElement({ children, anchor, isOpen, onClose, placement
79
79
  }
80
80
  return {};
81
81
  }, [width, resolvedAnchor]);
82
+ const setFloatingWithAnchorRegistry = useRegisterFloatingAnchor(refs.setFloating, resolvedAnchor instanceof Element ? resolvedAnchor : null);
82
83
  if (!isOpen) {
83
84
  return null;
84
85
  }
85
- return (_jsx(FloatingPortal, { children: _jsx(ConditionalScopedThemeProvider, { children: _jsx("div", { ref: refs.setFloating, className: cx(b(), className), style: {
86
+ return (_jsx(FloatingPortal, { children: _jsx(ConditionalScopedThemeProvider, { children: _jsx("div", { ref: setFloatingWithAnchorRegistry, className: cx(b(), className), style: {
86
87
  ...floatingStyles,
87
88
  ...getWidthStyle(),
88
89
  ...style,
@@ -0,0 +1,28 @@
1
+ import { type IUiFloatingElementProps } from "../UiFloatingElement/types.js";
2
+ /**
3
+ * - `"none"` — content owns its own padding.
4
+ * - `"listbox"` — `4px 0` so listbox-style popups breathe at top/bottom.
5
+ *
6
+ * @internal
7
+ */
8
+ export type UiFloatingPanelPadding = "none" | "listbox";
9
+ /**
10
+ * Props passed through to `UiFloatingElement`. The `Omit` narrows the surface
11
+ * away from props the canonical panel chrome owns or doesn't want callers to
12
+ * tune (`className` / `contentClassName` — chrome is bundled; positioning
13
+ * knobs reserved for low-level use).
14
+ *
15
+ * @internal
16
+ */
17
+ export interface IUiFloatingPanelProps extends Omit<IUiFloatingElementProps, "className" | "contentClassName" | "strategy" | "autoFlip" | "maxWidth" | "maxHeight" | "onPlacementChange"> {
18
+ /** Vertical padding bundled with the panel chrome. Defaults to `"none"`. */
19
+ padding?: UiFloatingPanelPadding;
20
+ }
21
+ /**
22
+ * `UiFloatingElement` plus the kit's canonical popup chrome (background,
23
+ * border-radius, shadow). Shared surface for dropdowns, comboboxes, pickers.
24
+ *
25
+ * @internal
26
+ */
27
+ export declare function UiFloatingPanel({ children, padding, ...rest }: IUiFloatingPanelProps): import("react/jsx-runtime").JSX.Element;
28
+ //# sourceMappingURL=UiFloatingPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UiFloatingPanel.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiFloatingPanel/UiFloatingPanel.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAK7E;;;;;GAKG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAC/C,uBAAuB,EACrB,WAAW,GACX,kBAAkB,GAClB,UAAU,GACV,UAAU,GACV,UAAU,GACV,WAAW,GACX,mBAAmB,CACxB;IACG,4EAA4E;IAC5E,OAAO,CAAC,EAAE,sBAAsB,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,OAAgB,EAAE,GAAG,IAAI,EAAE,EAAE,qBAAqB,2CAM7F"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // (C) 2026 GoodData Corporation
3
+ import { bem } from "../@utils/bem.js";
4
+ import { UiFloatingElement } from "../UiFloatingElement/UiFloatingElement.js";
5
+ const { b } = bem("gd-ui-kit-floating-panel");
6
+ /**
7
+ * `UiFloatingElement` plus the kit's canonical popup chrome (background,
8
+ * border-radius, shadow). Shared surface for dropdowns, comboboxes, pickers.
9
+ *
10
+ * @internal
11
+ */
12
+ export function UiFloatingPanel({ children, padding = "none", ...rest }) {
13
+ return (_jsx(UiFloatingElement, { ...rest, children: _jsx("div", { className: b({ padding }), children: children }) }));
14
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"UiTags.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTags/UiTags.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAO/D;;GAEG;AACH,wBAAgB,MAAM,CAAC,EACnB,IAAI,EACJ,UAAU,EACV,QAAoB,EACpB,SAAkB,EAClB,WAAsB,EACtB,UAAoB,EACpB,SAAkB,EAClB,WAAuB,EACvB,SAAuB,EACvB,WAAsB,EACtB,cAA+B,EAC/B,IAAoB,EACpB,IAAc,EACd,aAAoB,EACpB,YAAmB,EACnB,QAAgB,EAChB,UAAqB,EACrB,QAAmB,EACnB,WAAsB,EACtB,mBAAgD,EAChD,eAAe,EAClB,EAAE,YAAY,2CAoUd"}
1
+ {"version":3,"file":"UiTags.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTags/UiTags.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAO/D;;GAEG;AACH,wBAAgB,MAAM,CAAC,EACnB,IAAI,EACJ,UAAU,EACV,QAAoB,EACpB,SAAkB,EAClB,WAAsB,EACtB,UAAoB,EACpB,SAAkB,EAClB,WAAuB,EACvB,SAAuB,EACvB,WAAsB,EACtB,cAA+B,EAC/B,IAAoB,EACpB,IAAc,EACd,aAAoB,EACpB,YAAmB,EACnB,QAAgB,EAChB,UAAqB,EACrB,QAAmB,EACnB,WAAsB,EACtB,mBAAgD,EAChD,eAAe,EAClB,EAAE,YAAY,2CAmUd"}
@@ -94,7 +94,7 @@ export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Na
94
94
  _jsx(UiComboboxInput, { ref: (node) => {
95
95
  inputRef.current = node;
96
96
  interactionState.current.input = node;
97
- }, name: "add-input", className: e("add-input"), "aria-label": nameLabel, onKeyDown: (event) => {
97
+ }, name: "add-input", "aria-label": nameLabel, onKeyDown: (event) => {
98
98
  if (event.isDefaultPrevented()) {
99
99
  return;
100
100
  }
@@ -102,7 +102,7 @@ export function UiTags({ tags, tagOptions, addLabel = "Add tag", nameLabel = "Na
102
102
  onTagAddHandler();
103
103
  onClose();
104
104
  }
105
- } }), _jsx(UiComboboxPopup, { children: _jsx(UiComboboxList, { role: "none", children: (option, index) => (_jsxs(UiComboboxListItem, { option: option, index: index, children: [
105
+ } }), _jsx(UiComboboxPopup, { children: _jsx(UiComboboxList, { children: (option, index) => (_jsxs(UiComboboxListItem, { option: option, index: index, children: [
106
106
  _jsx(UiComboboxListItemLabel, { children: option.label }), option.creatable ? (_jsx(UiComboboxListItemCreatableLabel, { children: creatableLabel })) : null] }, option.id)) }) })
107
107
  ] })), footer: ({ onClose }) => (_jsxs(_Fragment, { children: [
108
108
  _jsx(UiButton, { label: cancelLabel, variant: "secondary", onClick: onClose, ref: (ref) => {
@@ -1,4 +1,4 @@
1
- import { type FocusEventHandler, type KeyboardEventHandler, type MouseEventHandler } from "react";
1
+ import { type FocusEventHandler, type KeyboardEventHandler, type MouseEventHandler, type Ref } from "react";
2
2
  import { type IAccessibilityConfigBase } from "../../typings/accessibility.js";
3
3
  import { type IconType } from "../@types/icon.js";
4
4
  /**
@@ -53,6 +53,17 @@ export interface IUiTextInputProps {
53
53
  onClick?: MouseEventHandler<HTMLInputElement>;
54
54
  /** Test id forwarded to the input element. */
55
55
  dataTestId?: string;
56
+ inputRef?: Ref<HTMLInputElement>;
57
+ /** Use as the floating-popup anchor when the popup should match the full field width. */
58
+ wrapperRef?: Ref<HTMLDivElement>;
59
+ name?: string;
60
+ /**
61
+ * Browser-autofill control. Combobox/autocomplete patterns should set
62
+ * `"off"` so the native dropdown doesn't overlap the listbox.
63
+ */
64
+ autoComplete?: string;
65
+ autoCapitalize?: string;
66
+ autoCorrect?: string;
56
67
  }
57
68
  /**
58
69
  * Single-line text input with optional label and leading / trailing icons.
@@ -61,5 +72,5 @@ export interface IUiTextInputProps {
61
72
  *
62
73
  * @internal
63
74
  */
64
- export declare function UiTextInput({ type, value, onChange, label, placeholder, iconBefore, iconAfter, onIconAfter, accessibilityConfig, disabled, autoFocus, onKeyDown, onFocus, onBlur, onClick, dataTestId }: IUiTextInputProps): import("react/jsx-runtime").JSX.Element;
75
+ export declare function UiTextInput({ type, value, onChange, label, placeholder, iconBefore, iconAfter, onIconAfter, accessibilityConfig, disabled, autoFocus, onKeyDown, onFocus, onBlur, onClick, dataTestId, inputRef, wrapperRef, name, autoComplete, autoCapitalize, autoCorrect }: IUiTextInputProps): import("react/jsx-runtime").JSX.Element;
65
76
  //# sourceMappingURL=UiTextInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"UiTextInput.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTextInput/UiTextInput.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EAEzB,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAOlD;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IACxC,uCAAuC;IACvC,IAAI,EAAE,QAAQ,CAAC;IACf,sBAAsB;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IAC3C,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,mFAAmF;IACnF,SAAS,CAAC,EAAE,QAAQ,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,2DAA2D;IAC3D,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gFAAgF;IAChF,SAAS,CAAC,EAAE,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IACnD,sCAAsC;IACtC,OAAO,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;IAC9C,sCAAsC;IACtC,MAAM,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;IAC7C,sCAAsC;IACtC,OAAO,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;IAC9C,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EACxB,IAAa,EACb,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAW,EACX,UAAU,EACV,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,QAAgB,EAChB,SAAiB,EACjB,SAAS,EACT,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACb,EAAE,iBAAiB,2CAuDnB"}
1
+ {"version":3,"file":"UiTextInput.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTextInput/UiTextInput.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,EACtB,KAAK,GAAG,EAEX,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAOlD;;;;;GAKG;AACH,MAAM,WAAW,2BAA2B;IACxC,uCAAuC;IACvC,IAAI,EAAE,QAAQ,CAAC;IACf,sBAAsB;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;IAC3C,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,mFAAmF;IACnF,SAAS,CAAC,EAAE,QAAQ,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,2BAA2B,CAAC;IAC1C,2DAA2D;IAC3D,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,gFAAgF;IAChF,SAAS,CAAC,EAAE,oBAAoB,CAAC,gBAAgB,CAAC,CAAC;IACnD,sCAAsC;IACtC,OAAO,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;IAC9C,sCAAsC;IACtC,MAAM,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;IAC7C,sCAAsC;IACtC,OAAO,CAAC,EAAE,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;IAC9C,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACjC,yFAAyF;IACzF,UAAU,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EACxB,IAAa,EACb,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAW,EACX,UAAU,EACV,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,QAAgB,EAChB,SAAiB,EACjB,SAAS,EACT,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,QAAQ,EACR,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,cAAc,EACd,WAAW,EACd,EAAE,iBAAiB,2CA4DnB"}
@@ -13,12 +13,12 @@ const { b, e } = bem("gd-ui-kit-text-input");
13
13
  *
14
14
  * @internal
15
15
  */
16
- export function UiTextInput({ type = "text", value, onChange, label, placeholder, iconBefore, iconAfter, onIconAfter, accessibilityConfig, disabled = false, autoFocus = false, onKeyDown, onFocus, onBlur, onClick, dataTestId, }) {
16
+ export function UiTextInput({ type = "text", value, onChange, label, placeholder, iconBefore, iconAfter, onIconAfter, accessibilityConfig, disabled = false, autoFocus = false, onKeyDown, onFocus, onBlur, onClick, dataTestId, inputRef, wrapperRef, name, autoComplete, autoCapitalize, autoCorrect, }) {
17
17
  const id = useId();
18
18
  const { ariaLabel, ...restA11y } = accessibilityConfig ?? {};
19
19
  // When a visible <label> is present, the label provides the accessible name;
20
20
  // forwarding aria-label would override it (WCAG name-in-name mismatch).
21
21
  const inputA11y = label ? restA11y : { ...restA11y, ariaLabel };
22
- return (_jsxs("div", { className: b(), children: [label ? (_jsx("label", { className: e("label"), htmlFor: id, children: label })) : null, _jsxs("div", { className: e("field", { disabled }), children: [iconBefore ? (_jsx("span", { className: e("icon-before"), children: _jsx(UiIcon, { type: iconBefore, size: 14, color: "complementary-6" }) })) : null, _jsx("input", { id: id, type: type, className: e("input"), value: value, placeholder: placeholder, disabled: disabled, autoFocus: autoFocus, "data-testid": dataTestId, onChange: (event) => onChange(event.target.value), onKeyDown: onKeyDown, onFocus: onFocus, onBlur: onBlur, onClick: onClick, ...accessibilityConfigToAttributes(inputA11y) }), onIconAfter ? (_jsx("span", { className: e("icon-after"), children: _jsx(UiIconButton, { icon: onIconAfter.icon, size: "xsmall", variant: "tertiary", onClick: onIconAfter.onClick, isDisabled: disabled, label: onIconAfter.ariaLabel, iconColor: "complementary-6" }) })) : iconAfter ? (_jsx("span", { className: e("icon-after"), children: _jsx(UiIcon, { type: iconAfter, size: 14, color: "complementary-6" }) })) : null] })
22
+ return (_jsxs("div", { ref: wrapperRef, className: b(), children: [label ? (_jsx("label", { className: e("label"), htmlFor: id, children: label })) : null, _jsxs("div", { className: e("field", { disabled }), children: [iconBefore ? (_jsx("span", { className: e("icon-before"), children: _jsx(UiIcon, { type: iconBefore, size: 14, color: "complementary-6" }) })) : null, _jsx("input", { ref: inputRef, id: id, name: name, type: type, className: e("input"), value: value, placeholder: placeholder, disabled: disabled, autoFocus: autoFocus, autoComplete: autoComplete, autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, "data-testid": dataTestId, onChange: (event) => onChange(event.target.value), onKeyDown: onKeyDown, onFocus: onFocus, onBlur: onBlur, onClick: onClick, ...accessibilityConfigToAttributes(inputA11y) }), onIconAfter ? (_jsx("span", { className: e("icon-after"), children: _jsx(UiIconButton, { icon: onIconAfter.icon, size: "xsmall", variant: "tertiary", onClick: onIconAfter.onClick, isDisabled: disabled, label: onIconAfter.ariaLabel, iconColor: "complementary-6" }) })) : iconAfter ? (_jsx("span", { className: e("icon-after"), children: _jsx(UiIcon, { type: iconAfter, size: 14, color: "complementary-6" }) })) : null] })
23
23
  ] }));
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"UiTooltip.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTooltip/UiTooltip.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAWnE;;GAEG;AACH,wBAAgB,SAAS,CAAC,EACtB,EAAE,EACF,MAAM,EACN,OAAO,EACP,SAAqB,EACrB,cAAsB,EACtB,SAAc,EACd,cAA2B,EAC3B,eAA4B,EAC5B,SAAgB,EAChB,KAAK,EACL,MAAM,EAAE,UAAU,EAClB,gBAAwB,EACxB,mBAAmB,EACnB,OAAmB,EACnB,QAAQ,EACR,MAAM,EAAE,UAAU,EAClB,MAAM,EACN,OAAO,EACP,mBAAmB,EACtB,EAAE,eAAe,2CAyKjB"}
1
+ {"version":3,"file":"UiTooltip.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTooltip/UiTooltip.tsx"],"names":[],"mappings":"AA2BA,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAWnE;;GAEG;AACH,wBAAgB,SAAS,CAAC,EACtB,EAAE,EACF,MAAM,EACN,OAAO,EACP,SAAqB,EACrB,cAAsB,EACtB,SAAc,EACd,cAA2B,EAC3B,eAA4B,EAC5B,SAAgB,EAChB,KAAK,EACL,MAAM,EAAE,UAAU,EAClB,gBAAwB,EACxB,mBAAmB,EACnB,OAAmB,EACnB,QAAQ,EACR,MAAM,EAAE,UAAU,EAClB,MAAM,EACN,OAAO,EACP,mBAAmB,EACtB,EAAE,eAAe,2CAmLjB"}
@@ -4,7 +4,7 @@ import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { FloatingArrow, FloatingPortal, safePolygon, useClick, useDismiss, useFocus, useHover, useInteractions, } from "@floating-ui/react";
5
5
  import { ConditionalScopedThemeProvider, useIsScopeThemed, useTheme } from "@gooddata/sdk-ui-theme-provider";
6
6
  import { bem } from "../@utils/bem.js";
7
- import { FLOATING_ELEMENT_DATA_ATTR } from "../hooks/useCloseOnOutsideClick.js";
7
+ import { FLOATING_ELEMENT_DATA_ATTR, isClickInsideOwnSubtree, useRegisterFloatingAnchor, } from "../hooks/useCloseOnOutsideClick.js";
8
8
  import { useFloatingPosition } from "../UiFloatingElement/useFloatingPosition.js";
9
9
  import { ARROW_HEIGHT, ARROW_WIDTH, HIDE_DELAY, SHOW_DELAY } from "./constants.js";
10
10
  import { computeArrowOffset, computeTooltipShift, getDimensionsFromRect, getFlipFallbackOrder, getOppositeBasicPlacement, } from "./utils.js";
@@ -93,8 +93,14 @@ export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPla
93
93
  const click = useClick(context, {
94
94
  enabled: triggerBy.includes("click"),
95
95
  });
96
- // Close on escape/outside click
97
- const dismiss = useDismiss(context, { enabled: isOpenProp === undefined });
96
+ // Read the anchor lazily — floating-ui's `refs.reference` is a mutable
97
+ // ref that only has a current value once the reference element mounts,
98
+ // which is independent of our render cycle.
99
+ const setFloatingWithAnchorRegistry = useRegisterFloatingAnchor(refs.setFloating, () => refs.reference.current instanceof Element ? refs.reference.current : null);
100
+ const dismiss = useDismiss(context, {
101
+ enabled: isOpenProp === undefined,
102
+ outsidePress: (event) => !isClickInsideOwnSubtree(event.target, refs.floating.current),
103
+ });
98
104
  const { getReferenceProps, getFloatingProps } = useInteractions([dismiss, hover, focus, click]);
99
105
  return (_jsxs(_Fragment, { children: [
100
106
  _jsx("div", { className: e("anchor"), ref: refs.setReference, style: anchorWrapperStyles, ...getReferenceProps(), children: anchor }), _jsx("span", { className: "sr-only", id: id, children: isOpen
@@ -104,7 +110,7 @@ export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPla
104
110
  : null }), isOpen ? (_jsx(FloatingPortal, { children: _jsx(ConditionalScopedThemeProvider, { children: _jsxs("div", { className: b({
105
111
  width: width === "same-as-anchor" ? "same-as-anchor" : false,
106
112
  variant,
107
- }), ref: refs.setFloating, style: {
113
+ }), ref: setFloatingWithAnchorRegistry, style: {
108
114
  zIndex,
109
115
  ...floatingStyles,
110
116
  width: width === "same-as-anchor" ? triggerDimensions?.width : width,
@@ -6,6 +6,37 @@ import { type ReferenceType } from "@floating-ui/react";
6
6
  * @internal
7
7
  */
8
8
  export declare const FLOATING_ELEMENT_DATA_ATTR = "data-gd-floating-element";
9
+ /**
10
+ * Register a floating panel ↔ anchor association. The dismiss hook walks
11
+ * the anchor's DOM ancestry to decide whether a click inside the panel is
12
+ * actually inside our subtree.
13
+ *
14
+ * @internal
15
+ */
16
+ export declare function registerFloatingAnchor(panel: Element | null, anchor: Element | null): void;
17
+ /**
18
+ * Wraps a floating-ui `setFloating` callback so the resulting ref also
19
+ * registers the panel↔anchor association in {@link floatingPanelAnchors}.
20
+ * Co-locates the registry write next to its consumer so every floating
21
+ * primitive gets nested-popup detection without re-implementing the wiring.
22
+ *
23
+ * Accepts the anchor as `Element | null` directly or as a getter — the
24
+ * getter form lets consumers read a floating-ui MutableRefObject lazily,
25
+ * at the moment the floating element mounts.
26
+ *
27
+ * @internal
28
+ */
29
+ export declare function useRegisterFloatingAnchor(setFloating: (node: HTMLElement | null) => void, anchor: Element | null | (() => Element | null)): (node: HTMLElement | null) => void;
30
+ /**
31
+ * Whether `target` lives inside the subtree owned by `ownFloating` — either
32
+ * directly (DOM containment) or transitively through a nested floating panel
33
+ * whose anchor chain leads back into `ownFloating`. Both portal to body, so
34
+ * DOM containment alone can't distinguish nested children from foreign
35
+ * siblings; the floating-panel-anchor registry bridges the gap.
36
+ *
37
+ * @internal
38
+ */
39
+ export declare function isClickInsideOwnSubtree(target: Element | null, ownFloating: Element | null): boolean;
9
40
  /**
10
41
  * @internal
11
42
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useCloseOnOutsideClick.d.ts","sourceRoot":"","sources":["../../../src/@ui/hooks/useCloseOnOutsideClick.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,OAAO,CAAC;AAElD,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC3C,WAAW,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC3C,SAAS,EAAE,SAAS,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC3C,cAAc,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;IAC7C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,EAAE,8BAA8B,GACxC,IAAI,CAqCN"}
1
+ {"version":3,"file":"useCloseOnOutsideClick.d.ts","sourceRoot":"","sources":["../../../src/@ui/hooks/useCloseOnOutsideClick.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAA0B,MAAM,OAAO,CAAC;AAE/D,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,6BAA6B,CAAC;AAarE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAO1F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CACrC,WAAW,EAAE,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,EAC/C,MAAM,EAAE,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,GAChD,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CASpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,EAAE,WAAW,EAAE,OAAO,GAAG,IAAI,GAAG,OAAO,CAWpG;AAED;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC3C,WAAW,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC3C,SAAS,EAAE,SAAS,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC3C,cAAc,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;IAC7C,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,IAAI,EACnB,OAAO,EAAE,8BAA8B,GACxC,IAAI,CAiCN"}
@@ -1,5 +1,5 @@
1
- // (C) 2025 GoodData Corporation
2
- import { useEffect } from "react";
1
+ // (C) 2025-2026 GoodData Corporation
2
+ import { useCallback, useEffect } from "react";
3
3
  import { isClickOnIgnoredElement } from "../UiFloatingElement/utils.js";
4
4
  // Stable empty array to avoid re-creating on every render
5
5
  const EMPTY_IGNORE_CLICKS_ON = [];
@@ -9,6 +9,76 @@ const EMPTY_IGNORE_CLICKS_ON = [];
9
9
  * @internal
10
10
  */
11
11
  export const FLOATING_ELEMENT_DATA_ATTR = "data-gd-floating-element";
12
+ /**
13
+ * Module-scope registry tracking each floating panel's anchor (reference)
14
+ * element. Lets `useCloseOnOutsideClick` distinguish a NESTED child popup
15
+ * (whose anchor lives inside our floating element) from an unrelated SIBLING
16
+ * popup that happens to be open at the same time — DOM containment alone
17
+ * can't tell them apart because both portal to `body`.
18
+ *
19
+ * @internal
20
+ */
21
+ const floatingPanelAnchors = new WeakMap();
22
+ /**
23
+ * Register a floating panel ↔ anchor association. The dismiss hook walks
24
+ * the anchor's DOM ancestry to decide whether a click inside the panel is
25
+ * actually inside our subtree.
26
+ *
27
+ * @internal
28
+ */
29
+ export function registerFloatingAnchor(panel, anchor) {
30
+ if (!panel)
31
+ return;
32
+ if (!anchor) {
33
+ floatingPanelAnchors.delete(panel);
34
+ return;
35
+ }
36
+ floatingPanelAnchors.set(panel, anchor);
37
+ }
38
+ /**
39
+ * Wraps a floating-ui `setFloating` callback so the resulting ref also
40
+ * registers the panel↔anchor association in {@link floatingPanelAnchors}.
41
+ * Co-locates the registry write next to its consumer so every floating
42
+ * primitive gets nested-popup detection without re-implementing the wiring.
43
+ *
44
+ * Accepts the anchor as `Element | null` directly or as a getter — the
45
+ * getter form lets consumers read a floating-ui MutableRefObject lazily,
46
+ * at the moment the floating element mounts.
47
+ *
48
+ * @internal
49
+ */
50
+ export function useRegisterFloatingAnchor(setFloating, anchor) {
51
+ return useCallback((node) => {
52
+ setFloating(node);
53
+ const anchorEl = typeof anchor === "function" ? anchor() : anchor;
54
+ registerFloatingAnchor(node, anchorEl);
55
+ }, [setFloating, anchor]);
56
+ }
57
+ /**
58
+ * Whether `target` lives inside the subtree owned by `ownFloating` — either
59
+ * directly (DOM containment) or transitively through a nested floating panel
60
+ * whose anchor chain leads back into `ownFloating`. Both portal to body, so
61
+ * DOM containment alone can't distinguish nested children from foreign
62
+ * siblings; the floating-panel-anchor registry bridges the gap.
63
+ *
64
+ * @internal
65
+ */
66
+ export function isClickInsideOwnSubtree(target, ownFloating) {
67
+ if (!target || !ownFloating)
68
+ return false;
69
+ if (ownFloating.contains(target))
70
+ return true;
71
+ let cursor = target.closest(`[${FLOATING_ELEMENT_DATA_ATTR}]`);
72
+ while (cursor) {
73
+ const anchor = floatingPanelAnchors.get(cursor);
74
+ if (!anchor)
75
+ return false;
76
+ if (ownFloating.contains(anchor))
77
+ return true;
78
+ cursor = anchor.closest(`[${FLOATING_ELEMENT_DATA_ATTR}]`);
79
+ }
80
+ return false;
81
+ }
12
82
  /**
13
83
  * Hook to trigger a callback when clicking outside of a target element.
14
84
  *
@@ -22,15 +92,12 @@ export function useCloseOnOutsideClick(isOpen, onClose, options) {
22
92
  }
23
93
  const handleClick = (event) => {
24
94
  const target = event.target;
25
- if (floatingRef.current?.contains(target)) {
95
+ if (isClickInsideOwnSubtree(target, floatingRef.current)) {
26
96
  return;
27
97
  }
28
98
  if (anchorRef.current instanceof Element && anchorRef.current.contains(target)) {
29
99
  return;
30
100
  }
31
- if (target.closest(`[${FLOATING_ELEMENT_DATA_ATTR}]`)) {
32
- return;
33
- }
34
101
  if (isClickOnIgnoredElement(target, ignoreClicksOn)) {
35
102
  return;
36
103
  }
@@ -2,5 +2,5 @@ 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, dashboardParameters, 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, dashboardLinkBasePath, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, onShareLinkCopy }: IShareDialogProps): import("react/jsx-runtime").JSX.Element;
6
6
  //# sourceMappingURL=ShareDialog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ShareDialog.d.ts","sourceRoot":"","sources":["../../../src/Dialog/ShareDialog/ShareDialog.tsx"],"names":[],"mappings":"AAwBA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;GAEG;AACH,wBAAgB,WAAW,CAAC,EACxB,OAAO,EACP,SAAS,EACT,MAAM,EACN,YAAY,EACZ,WAAW,EACX,OAAO,EACP,QAAQ,EACR,OAAO,EACP,aAAwB,EACxB,kBAAkB,EAClB,6BAA6B,EAC7B,qBAAqB,EACrB,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,uBAAuB,EACvB,sBAAsB,EACtB,eAAe,EAClB,EAAE,iBAAiB,2CAgHnB"}
1
+ {"version":3,"file":"ShareDialog.d.ts","sourceRoot":"","sources":["../../../src/Dialog/ShareDialog/ShareDialog.tsx"],"names":[],"mappings":"AAwBA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD;;GAEG;AACH,wBAAgB,WAAW,CAAC,EACxB,OAAO,EACP,SAAS,EACT,MAAM,EACN,YAAY,EACZ,WAAW,EACX,OAAO,EACP,QAAQ,EACR,OAAO,EACP,aAAwB,EACxB,kBAAkB,EAClB,6BAA6B,EAC7B,qBAAqB,EACrB,MAAM,EACN,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,sBAAsB,EACtB,eAAe,EAClB,EAAE,iBAAiB,2CAiHnB"}
@@ -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, dashboardParameters, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, onShareLinkCopy, }) {
12
+ export function ShareDialog({ backend, workspace, locale, sharedObject, currentUser, onApply, onCancel, onError, onInteraction = () => { }, isLockingSupported, isCurrentUserWorkspaceManager, isGranteeShareLoading, labels, currentUserPermissions, dashboardFilters, dashboardParameters, dashboardLinkBasePath, 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, dashboardParameters: dashboardParameters, 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, dashboardLinkBasePath: dashboardLinkBasePath, onCancel: onCancel, onSubmit: onSubmit, onError: onShareDialogBaseError, isShareGrantHidden: isShareGrantHidden, applyShareGrantOnSelect: applyShareGrantOnSelect, showDashboardShareLink: showDashboardShareLink, onShareLinkCopy: onShareLinkCopy, isGranteeShareLoading: isGranteeShareLoading }) }) }) }) }) }));
58
58
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ShareDialogBase.d.ts","sourceRoot":"","sources":["../../../../src/Dialog/ShareDialog/ShareDialogBase/ShareDialogBase.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKxD;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,qBAAqB,2CAoG3D"}
1
+ {"version":3,"file":"ShareDialogBase.d.ts","sourceRoot":"","sources":["../../../../src/Dialog/ShareDialog/ShareDialogBase/ShareDialogBase.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAKxD;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,qBAAqB,2CAsG3D"}
@@ -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, dashboardParameters, isCurrentUserWorkspaceManager, isShareGrantHidden, applyShareGrantOnSelect, showDashboardShareLink, isGranteeShareLoading, onShareLinkCopy, } = props;
14
+ const { onCancel, sharedObject, currentUser, currentUserPermissions, dashboardFilters, dashboardParameters, dashboardLinkBasePath, 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, 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 })) }) }));
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, dashboardLinkBasePath: dashboardLinkBasePath, 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
  }