@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.
- package/esm/@ui/UiCombobox/UiComboboxInput.d.ts +22 -4
- package/esm/@ui/UiCombobox/UiComboboxInput.d.ts.map +1 -1
- package/esm/@ui/UiCombobox/UiComboboxInput.js +38 -31
- package/esm/@ui/UiCombobox/UiComboboxList.js +2 -2
- package/esm/@ui/UiCombobox/UiComboboxListItem.d.ts +3 -13
- package/esm/@ui/UiCombobox/UiComboboxListItem.d.ts.map +1 -1
- package/esm/@ui/UiCombobox/UiComboboxListItem.js +13 -23
- package/esm/@ui/UiCombobox/UiComboboxPopup.d.ts +6 -3
- package/esm/@ui/UiCombobox/UiComboboxPopup.d.ts.map +1 -1
- package/esm/@ui/UiCombobox/UiComboboxPopup.js +5 -7
- package/esm/@ui/UiCombobox/types.d.ts +17 -9
- package/esm/@ui/UiCombobox/types.d.ts.map +1 -1
- package/esm/@ui/UiCombobox/useCombobox.d.ts.map +1 -1
- package/esm/@ui/UiCombobox/useCombobox.js +73 -161
- package/esm/@ui/UiCombobox/useComboboxChrome.d.ts +9 -0
- package/esm/@ui/UiCombobox/useComboboxChrome.d.ts.map +1 -0
- package/esm/@ui/UiCombobox/useComboboxChrome.js +61 -0
- package/esm/@ui/UiCombobox/useComboboxSelection.d.ts +24 -0
- package/esm/@ui/UiCombobox/useComboboxSelection.d.ts.map +1 -0
- package/esm/@ui/UiCombobox/useComboboxSelection.js +82 -0
- package/esm/@ui/UiDropdown/UiDropdown.js +3 -3
- package/esm/@ui/UiFloatingElement/UiFloatingElement.d.ts.map +1 -1
- package/esm/@ui/UiFloatingElement/UiFloatingElement.js +3 -2
- package/esm/@ui/UiFloatingPanel/UiFloatingPanel.d.ts +28 -0
- package/esm/@ui/UiFloatingPanel/UiFloatingPanel.d.ts.map +1 -0
- package/esm/@ui/UiFloatingPanel/UiFloatingPanel.js +14 -0
- package/esm/@ui/UiTags/UiTags.d.ts.map +1 -1
- package/esm/@ui/UiTags/UiTags.js +2 -2
- package/esm/@ui/UiTextInput/UiTextInput.d.ts +13 -2
- package/esm/@ui/UiTextInput/UiTextInput.d.ts.map +1 -1
- package/esm/@ui/UiTextInput/UiTextInput.js +2 -2
- package/esm/@ui/UiTooltip/UiTooltip.d.ts.map +1 -1
- package/esm/@ui/UiTooltip/UiTooltip.js +10 -4
- package/esm/@ui/hooks/useCloseOnOutsideClick.d.ts +31 -0
- package/esm/@ui/hooks/useCloseOnOutsideClick.d.ts.map +1 -1
- package/esm/@ui/hooks/useCloseOnOutsideClick.js +73 -6
- package/esm/Dialog/ShareDialog/ShareDialog.d.ts +1 -1
- package/esm/Dialog/ShareDialog/ShareDialog.d.ts.map +1 -1
- package/esm/Dialog/ShareDialog/ShareDialog.js +2 -2
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareDialogBase.d.ts.map +1 -1
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareDialogBase.js +2 -2
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.d.ts +1 -1
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.d.ts.map +1 -1
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareGranteeBase.js +2 -2
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.d.ts +1 -1
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.d.ts.map +1 -1
- package/esm/Dialog/ShareDialog/ShareDialogBase/ShareLink.js +3 -3
- package/esm/Dialog/ShareDialog/ShareDialogBase/types.d.ts +3 -0
- package/esm/Dialog/ShareDialog/ShareDialogBase/types.d.ts.map +1 -1
- package/esm/Dialog/ShareDialog/types.d.ts +5 -0
- package/esm/Dialog/ShareDialog/types.d.ts.map +1 -1
- package/esm/Header/generateHeaderMenuItemsGroups.js +3 -3
- package/esm/Tabs/Tabs.d.ts +2 -1
- package/esm/Tabs/Tabs.d.ts.map +1 -1
- package/esm/Tabs/Tabs.js +1 -1
- package/esm/index.d.ts +3 -2
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/sdk-ui-kit.d.ts +99 -33
- package/package.json +11 -11
- package/src/@ui/UiCombobox/UiCombobox.scss +0 -16
- package/src/@ui/UiDropdown/UiDropdown.scss +1 -20
- package/src/@ui/UiFloatingPanel/UiFloatingPanel.scss +27 -0
- package/src/@ui/UiMenu/UiMenu.scss +0 -1
- package/src/@ui/UiPopover/UiPopover.scss +8 -0
- package/src/@ui/UiTags/UiTags.scss +0 -4
- package/src/@ui/UiTextInput/UiTextInput.scss +6 -2
- package/src/@ui/index.scss +1 -0
- package/styles/css/list.css +6 -1
- package/styles/css/list.css.map +1 -1
- package/styles/css/main.css +24 -30
- package/styles/css/main.css.map +1 -1
- package/styles/css/menu.css +6 -1
- package/styles/css/menu.css.map +1 -1
- package/styles/icons/dashboard.svg +5 -0
- 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 {
|
|
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(
|
|
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:
|
|
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":"
|
|
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:
|
|
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,
|
|
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"}
|
package/esm/@ui/UiTags/UiTags.js
CHANGED
|
@@ -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",
|
|
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, {
|
|
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,
|
|
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":"
|
|
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
|
-
//
|
|
97
|
-
|
|
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:
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
}
|