@gooddata/sdk-ui-kit 11.57.0-alpha.3 → 11.57.0-alpha.5
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/@utils/keyboardNavigation.d.ts +10 -0
- package/esm/@ui/@utils/keyboardNavigation.d.ts.map +1 -1
- package/esm/@ui/@utils/keyboardNavigation.js +9 -0
- package/esm/@ui/UiAddGranteeDialog/UiAddGranteeDialogCard.d.ts +4 -6
- package/esm/@ui/UiAddGranteeDialog/UiAddGranteeDialogCard.d.ts.map +1 -1
- package/esm/@ui/UiAddGranteeDialog/UiAddGranteeDialogCard.js +10 -8
- package/esm/@ui/UiAutocomplete/useAsyncListSource.js +1 -1
- package/esm/@ui/UiGranteeAsyncPicker/UiGranteeAsyncPicker.d.ts +8 -1
- package/esm/@ui/UiGranteeAsyncPicker/UiGranteeAsyncPicker.d.ts.map +1 -1
- package/esm/@ui/UiGranteeAsyncPicker/UiGranteeAsyncPicker.js +10 -6
- package/esm/@ui/UiListbox/defaults/DefaultUiListboxInteractiveItemComponent.d.ts.map +1 -1
- package/esm/@ui/UiListbox/defaults/DefaultUiListboxInteractiveItemComponent.js +6 -2
- package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuContentItem.d.ts.map +1 -1
- package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuContentItem.js +7 -2
- package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuInteractiveItem.d.ts.map +1 -1
- package/esm/@ui/UiMenu/components/defaults/DefaultUiMenuInteractiveItem.js +7 -2
- package/esm/@ui/UiModalDialog/UiDialogHeader.d.ts.map +1 -1
- package/esm/@ui/UiModalDialog/UiDialogHeader.js +6 -3
- package/esm/@ui/UiObjectShareDialog/UiObjectShareDialogCard.d.ts +16 -3
- package/esm/@ui/UiObjectShareDialog/UiObjectShareDialogCard.d.ts.map +1 -1
- package/esm/@ui/UiObjectShareDialog/UiObjectShareDialogCard.js +2 -3
- package/esm/@ui/UiPermissionMenu/UiPermissionMenu.d.ts +9 -4
- package/esm/@ui/UiPermissionMenu/UiPermissionMenu.d.ts.map +1 -1
- package/esm/@ui/UiPermissionMenu/UiPermissionMenu.js +20 -29
- package/esm/@ui/UiTags/hooks/useResponsiveTags.js +1 -1
- package/esm/@ui/UiToolbar/UiToolbar.d.ts +52 -0
- package/esm/@ui/UiToolbar/UiToolbar.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/UiToolbar.js +44 -0
- package/esm/@ui/UiToolbar/UiToolbarChevron.d.ts +8 -0
- package/esm/@ui/UiToolbar/UiToolbarChevron.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/UiToolbarChevron.js +12 -0
- package/esm/@ui/UiToolbar/UiToolbarItemTooltip.d.ts +14 -0
- package/esm/@ui/UiToolbar/UiToolbarItemTooltip.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/UiToolbarItemTooltip.js +11 -0
- package/esm/@ui/UiToolbar/constants.d.ts +5 -0
- package/esm/@ui/UiToolbar/constants.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/constants.js +6 -0
- package/esm/@ui/UiToolbar/context.d.ts +23 -0
- package/esm/@ui/UiToolbar/context.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/context.js +6 -0
- package/esm/@ui/UiToolbar/rovingFocusUtils.d.ts +55 -0
- package/esm/@ui/UiToolbar/rovingFocusUtils.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/rovingFocusUtils.js +220 -0
- package/esm/@ui/UiToolbar/toolbarItemProps.d.ts +35 -0
- package/esm/@ui/UiToolbar/toolbarItemProps.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/toolbarItemProps.js +2 -0
- package/esm/@ui/UiToolbar/useToolbarRovingFocus.d.ts +19 -0
- package/esm/@ui/UiToolbar/useToolbarRovingFocus.d.ts.map +1 -0
- package/esm/@ui/UiToolbar/useToolbarRovingFocus.js +172 -0
- package/esm/@ui/UiToolbarButton/UiToolbarButton.d.ts +19 -0
- package/esm/@ui/UiToolbarButton/UiToolbarButton.d.ts.map +1 -0
- package/esm/@ui/UiToolbarButton/UiToolbarButton.js +29 -0
- package/esm/@ui/UiToolbarButton/useToolbarItemRole.d.ts +38 -0
- package/esm/@ui/UiToolbarButton/useToolbarItemRole.d.ts.map +1 -0
- package/esm/@ui/UiToolbarButton/useToolbarItemRole.js +67 -0
- package/esm/@ui/UiToolbarColorSwatch/UiToolbarColorSwatch.d.ts +38 -0
- package/esm/@ui/UiToolbarColorSwatch/UiToolbarColorSwatch.d.ts.map +1 -0
- package/esm/@ui/UiToolbarColorSwatch/UiToolbarColorSwatch.js +13 -0
- package/esm/@ui/UiToolbarDivider/UiToolbarDivider.d.ts +7 -0
- package/esm/@ui/UiToolbarDivider/UiToolbarDivider.d.ts.map +1 -0
- package/esm/@ui/UiToolbarDivider/UiToolbarDivider.js +12 -0
- package/esm/@ui/UiToolbarIconButton/UiToolbarIconButton.d.ts +34 -0
- package/esm/@ui/UiToolbarIconButton/UiToolbarIconButton.d.ts.map +1 -0
- package/esm/@ui/UiToolbarIconButton/UiToolbarIconButton.js +34 -0
- package/esm/@ui/UiToolbarIconSelect/UiToolbarIconSelect.d.ts +54 -0
- package/esm/@ui/UiToolbarIconSelect/UiToolbarIconSelect.d.ts.map +1 -0
- package/esm/@ui/UiToolbarIconSelect/UiToolbarIconSelect.js +26 -0
- package/esm/@ui/UiToolbarMoreButton/UiToolbarMoreButton.d.ts +41 -0
- package/esm/@ui/UiToolbarMoreButton/UiToolbarMoreButton.d.ts.map +1 -0
- package/esm/@ui/UiToolbarMoreButton/UiToolbarMoreButton.js +12 -0
- package/esm/@ui/UiToolbarSegmentedControl/UiToolbarSegmentedControl.d.ts +37 -0
- package/esm/@ui/UiToolbarSegmentedControl/UiToolbarSegmentedControl.d.ts.map +1 -0
- package/esm/@ui/UiToolbarSegmentedControl/UiToolbarSegmentedControl.js +145 -0
- package/esm/@ui/UiToolbarSegmentedControl/context.d.ts +12 -0
- package/esm/@ui/UiToolbarSegmentedControl/context.d.ts.map +1 -0
- package/esm/@ui/UiToolbarSegmentedControl/context.js +3 -0
- package/esm/@ui/UiToolbarSelect/UiToolbarSelect.d.ts +51 -0
- package/esm/@ui/UiToolbarSelect/UiToolbarSelect.d.ts.map +1 -0
- package/esm/@ui/UiToolbarSelect/UiToolbarSelect.js +24 -0
- package/esm/@ui/UiToolbarStepper/UiToolbarStepper.d.ts +75 -0
- package/esm/@ui/UiToolbarStepper/UiToolbarStepper.d.ts.map +1 -0
- package/esm/@ui/UiToolbarStepper/UiToolbarStepper.js +85 -0
- package/esm/@ui/UiTooltip/UiTooltip.d.ts +1 -1
- package/esm/@ui/UiTooltip/UiTooltip.d.ts.map +1 -1
- package/esm/@ui/UiTooltip/UiTooltip.js +26 -8
- package/esm/@ui/UiTooltip/types.d.ts +12 -0
- package/esm/@ui/UiTooltip/types.d.ts.map +1 -1
- package/esm/@ui/hooks/useElementSize.d.ts.map +1 -1
- package/esm/@ui/hooks/useElementSize.js +2 -1
- package/esm/@ui/hooks/useIsTextTruncated.d.ts +12 -0
- package/esm/@ui/hooks/useIsTextTruncated.d.ts.map +1 -0
- package/esm/@ui/hooks/useIsTextTruncated.js +29 -0
- package/esm/Dropdown/Dropdown.js +1 -1
- package/esm/EditableLabel/EditableLabel.js +1 -1
- package/esm/List/LegacyList.js +1 -1
- package/esm/List/List.js +1 -1
- package/esm/Messages/toasts/NonContextToastsInterop.js +1 -1
- package/esm/Messages/toasts/ToastsCenterMessage.d.ts.map +1 -1
- package/esm/Messages/toasts/ToastsCenterMessage.js +1 -1
- package/esm/ResponsiveText/ResponsiveText.js +3 -3
- package/esm/ShortenedText/ShortenedText.js +1 -1
- package/esm/index.d.ts +15 -1
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +12 -1
- package/esm/locales.d.ts +4 -16
- package/esm/locales.d.ts.map +1 -1
- package/esm/locales.js +2 -6
- package/esm/sdk-ui-kit.d.ts +513 -15
- package/esm/syntaxHighlightingInput/hooks/useCodemirror.js +1 -1
- package/esm/utils/domUtilities.d.ts +4 -0
- package/esm/utils/domUtilities.d.ts.map +1 -1
- package/esm/utils/domUtilities.js +4 -1
- package/esm/utils/events.d.ts +3 -0
- package/esm/utils/events.d.ts.map +1 -1
- package/esm/utils/events.js +4 -1
- package/package.json +13 -14
- package/src/@ui/UiAddGranteeDialog/UiAddGranteeDialog.scss +8 -0
- package/src/@ui/UiGranteeAsyncPicker/UiGranteeAsyncPicker.scss +0 -6
- package/src/@ui/UiGranteeRow/UiGranteeRow.scss +5 -0
- package/src/@ui/UiListbox/UiListbox.scss +4 -0
- package/src/@ui/UiMenu/UiMenu.scss +3 -0
- package/src/@ui/UiModalDialog/UiModalDialog.scss +16 -3
- package/src/@ui/UiObjectShareDialog/UiObjectShareDialog.scss +40 -1
- package/src/@ui/UiPermissionMenu/UiPermissionMenu.scss +7 -3
- package/src/@ui/UiToolbar/UiToolbar.scss +15 -0
- package/src/@ui/UiToolbar/UiToolbarChevron.scss +11 -0
- package/src/@ui/UiToolbar/toolbarItem.scss +133 -0
- package/src/@ui/UiToolbarButton/UiToolbarButton.scss +15 -0
- package/src/@ui/UiToolbarColorSwatch/UiToolbarColorSwatch.scss +66 -0
- package/src/@ui/UiToolbarDivider/UiToolbarDivider.scss +11 -0
- package/src/@ui/UiToolbarIconButton/UiToolbarIconButton.scss +30 -0
- package/src/@ui/UiToolbarIconSelect/UiToolbarIconSelect.scss +12 -0
- package/src/@ui/UiToolbarSegmentedControl/UiToolbarSegmentedControl.scss +10 -0
- package/src/@ui/UiToolbarSelect/UiToolbarSelect.scss +29 -0
- package/src/@ui/UiToolbarStepper/UiToolbarStepper.scss +53 -0
- package/src/@ui/defaultTheme.scss +1 -0
- package/src/@ui/index.scss +10 -0
- package/styles/css/main.css +563 -9
- package/styles/css/main.css.map +1 -1
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type UiToolbarNamingConfig } from "../UiToolbar/context.js";
|
|
3
|
+
import { type UiToolbarSegmentedControlChangeEvent } from "./context.js";
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export interface IUiToolbarSegmentedControlProps {
|
|
8
|
+
value: string | undefined;
|
|
9
|
+
onChange: (value: string, event: UiToolbarSegmentedControlChangeEvent) => void;
|
|
10
|
+
/**
|
|
11
|
+
* The group needs an accessible name.
|
|
12
|
+
*/
|
|
13
|
+
accessibilityConfig: UiToolbarNamingConfig;
|
|
14
|
+
isDisabled?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Two to five {@link UiToolbarButton} or {@link UiToolbarIconButton} items, each with a `value`.
|
|
17
|
+
*/
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
dataTestId?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Mutually exclusive choices rendered as one borderless strip. Arrow keys move the selection and
|
|
23
|
+
* the focus; the whole group is one tab stop. Standalone the arrows wrap; inside a {@link UiToolbar}
|
|
24
|
+
* they continue to the neighbouring toolbar item at the ends.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```
|
|
28
|
+
* <UiToolbarSegmentedControl value={mode} onChange={setMode} accessibilityConfig={{ ariaLabel: "Scope" }}>
|
|
29
|
+
* <UiToolbarButton value="cell" label="Cell" />
|
|
30
|
+
* <UiToolbarButton value="row" label="Row" />
|
|
31
|
+
* </UiToolbarSegmentedControl>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare const UiToolbarSegmentedControl: import("react").ForwardRefExoticComponent<IUiToolbarSegmentedControlProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
37
|
+
//# sourceMappingURL=UiToolbarSegmentedControl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UiToolbarSegmentedControl.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiToolbarSegmentedControl/UiToolbarSegmentedControl.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,SAAS,EAQjB,MAAM,OAAO,CAAC;AAOf,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAQrE,OAAO,EAEH,KAAK,oCAAoC,EAE5C,MAAM,cAAc,CAAC;AAEtB;;GAEG;AACH,MAAM,WAAW,+BAA+B;IAC5C,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,oCAAoC,KAAK,IAAI,CAAC;IAC/E;;OAEG;IACH,mBAAmB,EAAE,qBAAqB,CAAC;IAC3C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAiDD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,4HA6HrC,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, } from "react";
|
|
4
|
+
import { useAutoupdateRef } from "@gooddata/sdk-ui";
|
|
5
|
+
import { accessibilityConfigToAttributes } from "../../typings/utilities.js";
|
|
6
|
+
import { bem } from "../@utils/bem.js";
|
|
7
|
+
import { makeRadioGroupKeyboardNavigation } from "../@utils/keyboardNavigation.js";
|
|
8
|
+
import { TOOLBAR_COMPOSITE_ATTR, TOOLBAR_ROOT_ATTR, hasSystemModifier, isEligibleFocusable, } from "../UiToolbar/rovingFocusUtils.js";
|
|
9
|
+
import { UiToolbarSegmentedControlContext, } from "./context.js";
|
|
10
|
+
const { b } = bem("gd-ui-kit-toolbar-segmented-control");
|
|
11
|
+
// Decided from the DOM: a group rendered through a portal shares the toolbar's React tree but not
|
|
12
|
+
// its element tree, and only the element tree is what the toolbar's roving tabindex reaches. Only a
|
|
13
|
+
// UiToolbar manages the group; any other role="toolbar" ancestor does not.
|
|
14
|
+
function isInsideToolbar(group) {
|
|
15
|
+
return group.parentElement?.closest(`[${TOOLBAR_ROOT_ATTR}]`) != null;
|
|
16
|
+
}
|
|
17
|
+
function getRadios(group) {
|
|
18
|
+
return Array.from(group.querySelectorAll('[role="radio"]'));
|
|
19
|
+
}
|
|
20
|
+
const NAVIGABLE_RADIO = { isDisabledFocusable: false };
|
|
21
|
+
/**
|
|
22
|
+
* A radio the arrows may land on: rendered, visible and not disabled.
|
|
23
|
+
*/
|
|
24
|
+
function isNavigableRadio(group, radio) {
|
|
25
|
+
return isEligibleFocusable(group, radio, NAVIGABLE_RADIO);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Next enabled radio in the given direction. Wraps when `loop` is set, otherwise returns null at the ends.
|
|
29
|
+
*/
|
|
30
|
+
function findNextRadio(group, radios, from, delta, loop) {
|
|
31
|
+
// The current radio is never a candidate, so a wrap-around cannot re-select the same value.
|
|
32
|
+
const maxStep = from >= 0 ? radios.length - 1 : radios.length;
|
|
33
|
+
for (let step = 1; step <= maxStep; step++) {
|
|
34
|
+
const index = from + step * delta;
|
|
35
|
+
if (!loop && (index < 0 || index >= radios.length)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const candidate = radios[(index + radios.length) % radios.length];
|
|
39
|
+
if (candidate && isNavigableRadio(group, candidate)) {
|
|
40
|
+
return candidate;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Mutually exclusive choices rendered as one borderless strip. Arrow keys move the selection and
|
|
47
|
+
* the focus; the whole group is one tab stop. Standalone the arrows wrap; inside a {@link UiToolbar}
|
|
48
|
+
* they continue to the neighbouring toolbar item at the ends.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```
|
|
52
|
+
* <UiToolbarSegmentedControl value={mode} onChange={setMode} accessibilityConfig={{ ariaLabel: "Scope" }}>
|
|
53
|
+
* <UiToolbarButton value="cell" label="Cell" />
|
|
54
|
+
* <UiToolbarButton value="row" label="Row" />
|
|
55
|
+
* </UiToolbarSegmentedControl>
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export const UiToolbarSegmentedControl = forwardRef(function UiToolbarSegmentedControl({ value, onChange, accessibilityConfig, isDisabled = false, children, dataTestId }, ref) {
|
|
61
|
+
const groupRef = useRef(null);
|
|
62
|
+
useImperativeHandle(ref, () => groupRef.current, []);
|
|
63
|
+
const onChangeRef = useAutoupdateRef(onChange);
|
|
64
|
+
const contextValue = useMemo(() => ({ value, onChange, isDisabled }), [value, onChange, isDisabled]);
|
|
65
|
+
// Standalone the group owns the roving tabindex; inside a toolbar the toolbar does. Decided
|
|
66
|
+
// from the DOM, because a group rendered through a portal shares the toolbar's React tree
|
|
67
|
+
// but not its element tree.
|
|
68
|
+
const syncStandaloneTabIndex = useCallback(() => {
|
|
69
|
+
const group = groupRef.current;
|
|
70
|
+
if (!group || isInsideToolbar(group)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const radios = getRadios(group).filter((radio) => isNavigableRadio(group, radio));
|
|
74
|
+
const target = radios.find((radio) => radio.getAttribute("aria-checked") === "true") ?? radios[0];
|
|
75
|
+
for (const radio of getRadios(group)) {
|
|
76
|
+
radio.tabIndex = radio === target ? 0 : -1;
|
|
77
|
+
}
|
|
78
|
+
}, []);
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
syncStandaloneTabIndex();
|
|
81
|
+
});
|
|
82
|
+
// Media queries can hide a radio without a render; a viewport change is the cue.
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
window.addEventListener("resize", syncStandaloneTabIndex);
|
|
85
|
+
return () => window.removeEventListener("resize", syncStandaloneTabIndex);
|
|
86
|
+
}, [syncStandaloneTabIndex]);
|
|
87
|
+
// A radio can change its own eligibility without re-rendering the group.
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
const group = groupRef.current;
|
|
90
|
+
if (!group || typeof MutationObserver === "undefined") {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const observer = new MutationObserver(syncStandaloneTabIndex);
|
|
94
|
+
observer.observe(group, {
|
|
95
|
+
childList: true,
|
|
96
|
+
subtree: true,
|
|
97
|
+
attributes: true,
|
|
98
|
+
attributeFilter: [
|
|
99
|
+
"disabled",
|
|
100
|
+
"aria-disabled",
|
|
101
|
+
"aria-checked",
|
|
102
|
+
"aria-hidden",
|
|
103
|
+
"hidden",
|
|
104
|
+
"class",
|
|
105
|
+
"style",
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
return () => observer.disconnect();
|
|
109
|
+
}, [syncStandaloneTabIndex]);
|
|
110
|
+
const onKeyDown = useMemo(() => {
|
|
111
|
+
const step = (event, delta) => {
|
|
112
|
+
const group = groupRef.current;
|
|
113
|
+
if (!group || event.defaultPrevented || hasSystemModifier(event)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const radios = getRadios(group);
|
|
117
|
+
const current = event.target.closest('[role="radio"]');
|
|
118
|
+
const index = current ? radios.indexOf(current) : -1;
|
|
119
|
+
// Only the horizontal keys mean something to the enclosing toolbar, so only they hand
|
|
120
|
+
// off at the ends; vertical keys keep cycling inside the group.
|
|
121
|
+
const isHorizontal = event.code === "ArrowLeft" || event.code === "ArrowRight";
|
|
122
|
+
const loop = !isInsideToolbar(group) || !isHorizontal;
|
|
123
|
+
const next = findNextRadio(group, radios, index, delta, loop);
|
|
124
|
+
const nextValue = next?.dataset["value"];
|
|
125
|
+
if (!next || nextValue === undefined) {
|
|
126
|
+
// Only a horizontal key at the end of a toolbar group is handed on; every other
|
|
127
|
+
// arrow stays contained in the group so it cannot scroll the page.
|
|
128
|
+
if (loop) {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
event.stopPropagation();
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
event.stopPropagation();
|
|
136
|
+
onChangeRef.current(nextValue, event);
|
|
137
|
+
next.focus();
|
|
138
|
+
};
|
|
139
|
+
return makeRadioGroupKeyboardNavigation({
|
|
140
|
+
onFocusPrevious: (event) => step(event, -1),
|
|
141
|
+
onFocusNext: (event) => step(event, 1),
|
|
142
|
+
}, { shouldPreventDefault: false, shouldStopPropagation: false });
|
|
143
|
+
}, [onChangeRef]);
|
|
144
|
+
return (_jsx(UiToolbarSegmentedControlContext.Provider, { value: contextValue, children: _jsx("div", { ref: groupRef, className: b(), ...accessibilityConfigToAttributes(accessibilityConfig), role: "radiogroup", "aria-disabled": isDisabled || undefined, "data-testid": dataTestId, onKeyDown: onKeyDown, [TOOLBAR_COMPOSITE_ATTR]: "", children: children }) }));
|
|
145
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type KeyboardEvent, type MouseEvent } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* @internal
|
|
4
|
+
*/
|
|
5
|
+
export type UiToolbarSegmentedControlChangeEvent = MouseEvent<HTMLElement> | KeyboardEvent<HTMLElement>;
|
|
6
|
+
export interface IUiToolbarSegmentedControlContextValue {
|
|
7
|
+
value: string | undefined;
|
|
8
|
+
onChange: (value: string, event: UiToolbarSegmentedControlChangeEvent) => void;
|
|
9
|
+
isDisabled: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const UiToolbarSegmentedControlContext: import("react").Context<IUiToolbarSegmentedControlContextValue | null>;
|
|
12
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiToolbarSegmentedControl/context.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAiB,MAAM,OAAO,CAAC;AAE3E;;GAEG;AACH,MAAM,MAAM,oCAAoC,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;AAExG,MAAM,WAAW,sCAAsC;IACnD,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,oCAAoC,KAAK,IAAI,CAAC;IAC/E,UAAU,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,gCAAgC,wEAE5C,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type KeyboardEvent, type MouseEvent, type ReactNode } from "react";
|
|
2
|
+
import { type IAccessibilityConfigBase } from "../../typings/accessibility.js";
|
|
3
|
+
import { type IUiDropdownButtonRenderProps } from "../UiDropdown/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export interface IUiToolbarSelectProps {
|
|
8
|
+
/**
|
|
9
|
+
* The current selection, or the prompt when `isPlaceholder` is set.
|
|
10
|
+
*/
|
|
11
|
+
label: string;
|
|
12
|
+
/**
|
|
13
|
+
* "fixed" is 240px wide and truncates the label so the toolbar does not move as the selection
|
|
14
|
+
* changes.
|
|
15
|
+
* @defaultValue "hug"
|
|
16
|
+
*/
|
|
17
|
+
width?: "hug" | "fixed";
|
|
18
|
+
/**
|
|
19
|
+
* Nothing is chosen yet; the label reads as prompt text.
|
|
20
|
+
* @defaultValue false
|
|
21
|
+
*/
|
|
22
|
+
isPlaceholder?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Held while the popup is on screen. Flips the chevron and keeps the pressed look.
|
|
25
|
+
* @defaultValue false
|
|
26
|
+
*/
|
|
27
|
+
isOpen?: boolean;
|
|
28
|
+
isDisabled?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Shown on hover and focus, for example the full name of a truncated label.
|
|
31
|
+
*/
|
|
32
|
+
tooltip?: ReactNode;
|
|
33
|
+
onClick?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
34
|
+
onKeyDown?: (event: KeyboardEvent<HTMLButtonElement>) => void;
|
|
35
|
+
id?: string;
|
|
36
|
+
dataId?: string;
|
|
37
|
+
dataTestId?: string;
|
|
38
|
+
accessibilityConfig?: IAccessibilityConfigBase;
|
|
39
|
+
/**
|
|
40
|
+
* Attributes from the `renderButton` callback of {@link UiDropdown}. Without them the trigger
|
|
41
|
+
* announces a listbox popup.
|
|
42
|
+
*/
|
|
43
|
+
ariaAttributes?: IUiDropdownButtonRenderProps["ariaAttributes"];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Label-driven dropdown trigger of a toolbar.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export declare const UiToolbarSelect: import("react").ForwardRefExoticComponent<IUiToolbarSelectProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
51
|
+
//# sourceMappingURL=UiToolbarSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UiToolbarSelect.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiToolbarSelect/UiToolbarSelect.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAc,MAAM,OAAO,CAAC;AAIxF,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAG/E,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAK3E;;GAEG;AACH,MAAM,WAAW,qBAAqB;IAClC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IACxB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACzD,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC9D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C;;;OAGG;IACH,cAAc,CAAC,EAAE,4BAA4B,CAAC,gBAAgB,CAAC,CAAC;CACnE;AAID;;;;GAIG;AACH,eAAO,MAAM,eAAe,qHAkD1B,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import { simplifyText } from "@gooddata/util";
|
|
5
|
+
import { accessibilityConfigToAttributes } from "../../typings/utilities.js";
|
|
6
|
+
import { bem } from "../@utils/bem.js";
|
|
7
|
+
import { UiToolbarChevron } from "../UiToolbar/UiToolbarChevron.js";
|
|
8
|
+
import { UiToolbarItemTooltip } from "../UiToolbar/UiToolbarItemTooltip.js";
|
|
9
|
+
import { useToolbarItemRole } from "../UiToolbarButton/useToolbarItemRole.js";
|
|
10
|
+
const { b, e } = bem("gd-ui-kit-toolbar-select");
|
|
11
|
+
/**
|
|
12
|
+
* Label-driven dropdown trigger of a toolbar.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export const UiToolbarSelect = forwardRef(function UiToolbarSelect({ label, width = "hug", isPlaceholder = false, isOpen = false, isDisabled, tooltip, onClick, onKeyDown, id, dataId, dataTestId, accessibilityConfig, ariaAttributes, }, ref) {
|
|
17
|
+
const { buttonProps } = useToolbarItemRole({ isDisabled, onClick, onKeyDown });
|
|
18
|
+
const testId = dataTestId ?? simplifyText(label);
|
|
19
|
+
const button = (_jsxs("button", { ref: ref, id: id, type: "button", className: b({ width, isPlaceholder, isActive: isOpen }), ...accessibilityConfigToAttributes(accessibilityConfig), title: width === "fixed" && tooltip === undefined ? label : undefined, "data-id": dataId, "data-testid": testId, "aria-haspopup": accessibilityConfig?.ariaHaspopup ?? "listbox", "aria-expanded": accessibilityConfig?.ariaExpanded ?? isOpen, ...buttonProps, ...ariaAttributes, children: [_jsx("span", { className: e("label"), children: label }), _jsx(UiToolbarChevron, { isOpen: isOpen })] }));
|
|
20
|
+
if (tooltip === undefined) {
|
|
21
|
+
return button;
|
|
22
|
+
}
|
|
23
|
+
return (_jsx(UiToolbarItemTooltip, { content: tooltip, isDisabled: isOpen, children: button }));
|
|
24
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type KeyboardEvent, type MouseEvent, type Ref } from "react";
|
|
2
|
+
import { type IconType } from "../@types/icon.js";
|
|
3
|
+
import { type IUiDropdownButtonRenderProps } from "../UiDropdown/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export interface IUiToolbarStepperAccessibilityConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Name of the value, for example "Zoom" or "Page".
|
|
10
|
+
*/
|
|
11
|
+
ariaLabel: string;
|
|
12
|
+
/**
|
|
13
|
+
* Name of the increase control, for example "Zoom in" or "Next page".
|
|
14
|
+
*/
|
|
15
|
+
incrementLabel: string;
|
|
16
|
+
/**
|
|
17
|
+
* Name of the decrease control, for example "Zoom out" or "Previous page".
|
|
18
|
+
*/
|
|
19
|
+
decrementLabel: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export interface IUiToolbarStepperProps {
|
|
25
|
+
/**
|
|
26
|
+
* "value" adjusts a quantity with minus and plus. "pagination" moves through a sequence with
|
|
27
|
+
* left and right arrows and shows the value as a readout.
|
|
28
|
+
*/
|
|
29
|
+
variant: "value" | "pagination";
|
|
30
|
+
/**
|
|
31
|
+
* The committed value as text, for example "100%" or "1 / 2".
|
|
32
|
+
*/
|
|
33
|
+
value: string;
|
|
34
|
+
onStep: (direction: 1 | -1) => void;
|
|
35
|
+
/**
|
|
36
|
+
* @defaultValue true
|
|
37
|
+
*/
|
|
38
|
+
canStepUp?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* @defaultValue true
|
|
41
|
+
*/
|
|
42
|
+
canStepDown?: boolean;
|
|
43
|
+
isDisabled?: boolean;
|
|
44
|
+
accessibilityConfig: IUiToolbarStepperAccessibilityConfig;
|
|
45
|
+
decreaseIcon?: IconType;
|
|
46
|
+
increaseIcon?: IconType;
|
|
47
|
+
/**
|
|
48
|
+
* Makes the value of the "value" variant editable. Enter commits the typed text. Escape and blur
|
|
49
|
+
* revert the typed text to the committed value.
|
|
50
|
+
*/
|
|
51
|
+
onCommit?: (value: string) => void;
|
|
52
|
+
/**
|
|
53
|
+
* Held while the preset list is on screen.
|
|
54
|
+
* @defaultValue false
|
|
55
|
+
*/
|
|
56
|
+
isOpen?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Attributes from the `renderButton` callback of a {@link UiDropdown} with `triggerRole: "combobox"`.
|
|
59
|
+
*/
|
|
60
|
+
ariaAttributes?: IUiDropdownButtonRenderProps["ariaAttributes"];
|
|
61
|
+
inputRef?: Ref<HTMLInputElement>;
|
|
62
|
+
onClick?: (event: MouseEvent<HTMLInputElement>) => void;
|
|
63
|
+
/**
|
|
64
|
+
* Runs before the built-in key handling, which is skipped when the event is default-prevented.
|
|
65
|
+
*/
|
|
66
|
+
onInputKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
|
|
67
|
+
dataTestId?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Two side controls around a value. Each part is its own toolbar stop.
|
|
71
|
+
*
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export declare const UiToolbarStepper: import("react").ForwardRefExoticComponent<IUiToolbarStepperProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
75
|
+
//# sourceMappingURL=UiToolbarStepper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UiToolbarStepper.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiToolbarStepper/UiToolbarStepper.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,KAAK,GAAG,EAAwB,MAAM,OAAO,CAAC;AAE9G,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,OAAO,EAAE,KAAK,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAK3E;;GAEG;AACH,MAAM,WAAW,oCAAoC;IACjD;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC;;;OAGG;IACH,OAAO,EAAE,OAAO,GAAG,YAAY,CAAC;IAChC;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,EAAE,oCAAoC,CAAC;IAC1D,YAAY,CAAC,EAAE,QAAQ,CAAC;IACxB,YAAY,CAAC,EAAE,QAAQ,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;OAEG;IACH,cAAc,CAAC,EAAE,4BAA4B,CAAC,gBAAgB,CAAC,CAAC;IAChE,QAAQ,CAAC,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IACxD;;OAEG;IACH,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAsBD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,mHA8H3B,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// (C) 2026 GoodData Corporation
|
|
3
|
+
import { forwardRef, useState } from "react";
|
|
4
|
+
import { bem } from "../@utils/bem.js";
|
|
5
|
+
import { hasSystemModifier } from "../UiToolbar/rovingFocusUtils.js";
|
|
6
|
+
import { isPopupKey } from "../UiToolbarButton/useToolbarItemRole.js";
|
|
7
|
+
import { UiToolbarIconButton } from "../UiToolbarIconButton/UiToolbarIconButton.js";
|
|
8
|
+
const { b, e } = bem("gd-ui-kit-toolbar-stepper");
|
|
9
|
+
// The side buttons are not the popup trigger. An enclosing dropdown wrapper would otherwise toggle
|
|
10
|
+
// its popup on these keys and cancel the buttons' native click.
|
|
11
|
+
function keepPopupKeys(event) {
|
|
12
|
+
if (!isPopupKey(event)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
event.stopPropagation();
|
|
16
|
+
// Enter and Space keep their default so the button still clicks; the arrows would only scroll.
|
|
17
|
+
if (event.code === "ArrowUp" || event.code === "ArrowDown") {
|
|
18
|
+
event.preventDefault();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_ICONS = {
|
|
22
|
+
value: { decrease: "minus", increase: "plus" },
|
|
23
|
+
pagination: { decrease: "navigateLeft", increase: "navigateRight" },
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Two side controls around a value. Each part is its own toolbar stop.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export const UiToolbarStepper = forwardRef(function UiToolbarStepper({ variant, value, onStep, canStepUp = true, canStepDown = true, isDisabled = false, accessibilityConfig, decreaseIcon, increaseIcon, onCommit, isOpen = false, ariaAttributes, inputRef, onClick, onInputKeyDown, dataTestId, }, ref) {
|
|
31
|
+
const isEditable = variant === "value" && onCommit !== undefined;
|
|
32
|
+
const [draft, setDraft] = useState(value);
|
|
33
|
+
const [committed, setCommitted] = useState(value);
|
|
34
|
+
if (committed !== value) {
|
|
35
|
+
setCommitted(value);
|
|
36
|
+
setDraft(value);
|
|
37
|
+
}
|
|
38
|
+
const handleKeyDown = (event) => {
|
|
39
|
+
// A disabled value is read-only but focusable; the keys that would open an enclosing
|
|
40
|
+
// dropdown are swallowed so the popup stays closed.
|
|
41
|
+
if (isDisabled) {
|
|
42
|
+
if (isPopupKey(event)) {
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
event.stopPropagation();
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
onInputKeyDown?.(event);
|
|
49
|
+
if (event.defaultPrevented) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// While an IME is composing, the arrows walk the candidate list and Enter accepts a
|
|
53
|
+
// candidate. Those presses belong to the input method, not to the stepper.
|
|
54
|
+
if (event.nativeEvent.isComposing) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// Modified arrows are left alone: Alt+ArrowDown opens the preset list of a combobox and
|
|
58
|
+
// the other combinations belong to the browser or the OS.
|
|
59
|
+
if (event.code === "ArrowUp" && !hasSystemModifier(event)) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
event.stopPropagation();
|
|
62
|
+
if (canStepUp) {
|
|
63
|
+
onStep(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (event.code === "ArrowDown" && !hasSystemModifier(event)) {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
event.stopPropagation();
|
|
69
|
+
if (canStepDown) {
|
|
70
|
+
onStep(-1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else if (event.code === "Enter") {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
event.stopPropagation();
|
|
76
|
+
onCommit?.(draft);
|
|
77
|
+
}
|
|
78
|
+
else if (event.code === "Escape") {
|
|
79
|
+
// Not stopped: an enclosing dropdown closes its preset list on the same key.
|
|
80
|
+
setDraft(value);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const icons = DEFAULT_ICONS[variant];
|
|
84
|
+
return (_jsxs("div", { ref: ref, className: b({ variant }), "data-testid": dataTestId, children: [_jsx(UiToolbarIconButton, { size: "small", icon: decreaseIcon ?? icons.decrease, label: accessibilityConfig.decrementLabel, isDisabled: isDisabled || !canStepDown, onClick: () => onStep(-1), onKeyDown: keepPopupKeys }), isEditable ? (_jsx("input", { ref: inputRef, className: e("value", { isEditable, isActive: isOpen }), type: "text", inputMode: "decimal", value: draft, "aria-label": accessibilityConfig.ariaLabel, "aria-disabled": isDisabled || undefined, readOnly: isDisabled, onChange: (event) => setDraft(event.target.value), onBlur: () => setDraft(value), onClick: isDisabled ? undefined : onClick, onKeyDown: handleKeyDown, ...ariaAttributes })) : (_jsx("span", { className: e("value", { isEditable }), role: "status", "aria-label": accessibilityConfig.ariaLabel, children: value })), isEditable ? (_jsx("span", { className: "sr-only", role: "status", "aria-label": accessibilityConfig.ariaLabel, children: value })) : null, _jsx(UiToolbarIconButton, { size: "small", icon: increaseIcon ?? icons.increase, label: accessibilityConfig.incrementLabel, isDisabled: isDisabled || !canStepUp, onClick: () => onStep(1), onKeyDown: keepPopupKeys })] }));
|
|
85
|
+
});
|
|
@@ -2,5 +2,5 @@ import { type IUiTooltipProps } from "./types.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* @internal
|
|
4
4
|
*/
|
|
5
|
-
export declare function UiTooltip({ id, anchor, content, behaviour, arrowPlacement, triggerBy, hoverOpenDelay, hoverCloseDelay, showArrow, width, offset: offsetProp, optimalPlacement, accessibilityConfig, accessibilityHidden, variant, disabled, isOpen: isOpenProp, onOpen, onClose, onOpenChange, component, inlineAnchor, anchorWrapperStyles, }: IUiTooltipProps): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export declare function UiTooltip({ id, anchor, content, behaviour, arrowPlacement, triggerBy, hoverOpenDelay, hoverCloseDelay, showArrow, width, offset: offsetProp, optimalPlacement, accessibilityConfig, accessibilityHidden, variant, disabled, isOpen: isOpenProp, onOpen, onClose, onOpenChange, component, inlineAnchor, anchorWrapperStyles, closeOnAnchorClick, delayGroup, }: IUiTooltipProps): import("react/jsx-runtime").JSX.Element;
|
|
6
6
|
//# sourceMappingURL=UiTooltip.d.ts.map
|
|
@@ -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":"AA6BA,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,mBAA2B,EAC3B,OAAmB,EACnB,QAAQ,EACR,MAAM,EAAE,UAAU,EAClB,MAAM,EACN,OAAO,EACP,YAAY,EACZ,SAAiB,EACjB,YAAoB,EACpB,mBAAmB,EACnB,kBAA0B,EAC1B,UAAkB,GACrB,EAAE,eAAe,2CA+NjB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
// (C) 2025-2026 GoodData Corporation
|
|
3
3
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
4
|
-
import { FloatingArrow, FloatingPortal, safePolygon, useClick, useFocus, useHover, useInteractions, } from "@floating-ui/react";
|
|
4
|
+
import { FloatingArrow, FloatingPortal, safePolygon, useClick, useDelayGroup, 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
7
|
import { useCloseOnEscape } from "../hooks/useCloseOnEscape.js";
|
|
@@ -13,7 +13,7 @@ const { b, e } = bem("gd-ui-kit-tooltip");
|
|
|
13
13
|
/**
|
|
14
14
|
* @internal
|
|
15
15
|
*/
|
|
16
|
-
export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPlacement = "top", triggerBy = [], hoverOpenDelay = SHOW_DELAY, hoverCloseDelay = HIDE_DELAY, showArrow = true, width, offset: offsetProp, optimalPlacement = false, accessibilityConfig, accessibilityHidden = false, variant = "default", disabled, isOpen: isOpenProp, onOpen, onClose, onOpenChange, component = "div", inlineAnchor = false, anchorWrapperStyles, }) {
|
|
16
|
+
export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPlacement = "top", triggerBy = [], hoverOpenDelay = SHOW_DELAY, hoverCloseDelay = HIDE_DELAY, showArrow = true, width, offset: offsetProp, optimalPlacement = false, accessibilityConfig, accessibilityHidden = false, variant = "default", disabled, isOpen: isOpenProp, onOpen, onClose, onOpenChange, component = "div", inlineAnchor = false, anchorWrapperStyles, closeOnAnchorClick = false, delayGroup = false, }) {
|
|
17
17
|
const [isOpenInternal, setIsOpen] = useState(false);
|
|
18
18
|
const isControlled = isOpenProp !== undefined;
|
|
19
19
|
const isOpen = !disabled && (isControlled ? isOpenProp : isOpenInternal || triggerBy.length === 0);
|
|
@@ -68,7 +68,7 @@ export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPla
|
|
|
68
68
|
const triggerDimensions = useMemo(() => {
|
|
69
69
|
const rect = refs.reference.current?.getBoundingClientRect?.();
|
|
70
70
|
return getDimensionsFromRect(rect ?? null);
|
|
71
|
-
}, [refs.reference, isOpen]); //
|
|
71
|
+
}, [refs.reference, isOpen]); // oxlint-disable-line react-hooks/exhaustive-deps
|
|
72
72
|
// Use state + layoutEffect to get dimensions AFTER DOM paints
|
|
73
73
|
// This fixes arrow positioning for *-end placements on initial render
|
|
74
74
|
const [floatingDimensions, setFloatingDimensions] = useState({ width: 0, height: 0 });
|
|
@@ -86,14 +86,18 @@ export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPla
|
|
|
86
86
|
// triggers are suppressed — otherwise an anchor that is shared with another
|
|
87
87
|
// popover (e.g. the labels picker reusing the permission-menu button) would
|
|
88
88
|
// toggle both.
|
|
89
|
+
// useHover does not read the group delay on its own; it has to be passed in.
|
|
90
|
+
const group = useDelayGroup(context, { enabled: delayGroup });
|
|
89
91
|
const hover = useHover(context, {
|
|
90
92
|
enabled: !isControlled && triggerBy.includes("hover"),
|
|
91
93
|
move: false,
|
|
92
94
|
handleClose: safePolygon({ requireIntent: true }),
|
|
93
|
-
delay:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
delay: delayGroup
|
|
96
|
+
? group.delay
|
|
97
|
+
: {
|
|
98
|
+
open: hoverOpenDelay,
|
|
99
|
+
close: hoverCloseDelay,
|
|
100
|
+
},
|
|
97
101
|
});
|
|
98
102
|
const focus = useFocus(context, {
|
|
99
103
|
enabled: !isControlled && triggerBy.includes("focus"),
|
|
@@ -101,6 +105,15 @@ export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPla
|
|
|
101
105
|
const click = useClick(context, {
|
|
102
106
|
enabled: !isControlled && triggerBy.includes("click"),
|
|
103
107
|
});
|
|
108
|
+
// Outside press and Escape stay with the DOM-based hooks below. Stays enabled while closed:
|
|
109
|
+
// the close it emits cancels a hover open that is still waiting on its delay.
|
|
110
|
+
const anchorPressDismiss = useDismiss(context, {
|
|
111
|
+
enabled: closeOnAnchorClick && !isControlled,
|
|
112
|
+
referencePress: true,
|
|
113
|
+
referencePressEvent: "click",
|
|
114
|
+
outsidePress: false,
|
|
115
|
+
escapeKey: false,
|
|
116
|
+
});
|
|
104
117
|
// Read the anchor lazily — floating-ui's `refs.reference` is a mutable
|
|
105
118
|
// ref that only has a current value once the reference element mounts,
|
|
106
119
|
// which is independent of our render cycle.
|
|
@@ -119,7 +132,12 @@ export function UiTooltip({ id, anchor, content, behaviour = "tooltip", arrowPla
|
|
|
119
132
|
anchorRef: refs.reference,
|
|
120
133
|
});
|
|
121
134
|
useCloseOnEscape(isOpen, handleClose);
|
|
122
|
-
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
135
|
+
const { getReferenceProps, getFloatingProps } = useInteractions([
|
|
136
|
+
hover,
|
|
137
|
+
focus,
|
|
138
|
+
click,
|
|
139
|
+
anchorPressDismiss,
|
|
140
|
+
]);
|
|
123
141
|
const Component = component === "div" ? "div" : "span";
|
|
124
142
|
return (_jsxs(_Fragment, { children: [_jsx(Component, { className: e("anchor", { inline: inlineAnchor }), ref: refs.setReference, style: anchorWrapperStyles, ...getReferenceProps(), children: anchor }), accessibilityHidden ? null : (_jsx("span", { className: "sr-only", id: id, children: isOpen
|
|
125
143
|
? typeof content === "function"
|
|
@@ -92,6 +92,18 @@ export interface IUiTooltipProps {
|
|
|
92
92
|
* Custom styles for the anchor wrapper element
|
|
93
93
|
*/
|
|
94
94
|
anchorWrapperStyles?: CSSProperties;
|
|
95
|
+
/**
|
|
96
|
+
* Close the tooltip when the anchor is clicked. Uncontrolled mode only.
|
|
97
|
+
* @defaultValue false
|
|
98
|
+
*/
|
|
99
|
+
closeOnAnchorClick?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Take the hover delays from an enclosing floating-ui `FloatingDelayGroup` instead of
|
|
102
|
+
* `hoverOpenDelay` / `hoverCloseDelay`. Sibling tooltips in the group open instantly while one
|
|
103
|
+
* of them was recently open.
|
|
104
|
+
* @defaultValue false
|
|
105
|
+
*/
|
|
106
|
+
delayGroup?: boolean;
|
|
95
107
|
}
|
|
96
108
|
export type Dimensions = {
|
|
97
109
|
width: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTooltip/types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE3D,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAC3B,KAAK,GACL,WAAW,GACX,SAAS,GACT,QAAQ,GACR,cAAc,GACd,YAAY,GACZ,MAAM,GACN,YAAY,GACZ,UAAU,GACV,OAAO,GACP,aAAa,GACb,WAAW,CAAC;AAElB;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,yBAAyB;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,uGAAuG;IACvG,cAAc,CAAC,EAAE,qBAAqB,CAAC;IAEvC,6DAA6D;IAC7D,MAAM,EAAE,SAAS,CAAC;IAElB,gDAAgD;IAChD,OAAO,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAA;KAAE,KAAK,SAAS,CAAC,CAAC;IAEpG,2DAA2D;IAC3D,SAAS,CAAC,EAAE,KAAK,CAAC,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC;IAE/C,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,kEAAkE;IAClE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,wCAAwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAElC,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,+FAA+F;IAC/F,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAElC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAEvC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,mBAAmB,CAAC,EAAE,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiTooltip/types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE3D,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAC3B,KAAK,GACL,WAAW,GACX,SAAS,GACT,QAAQ,GACR,cAAc,GACd,YAAY,GACZ,MAAM,GACN,YAAY,GACZ,UAAU,GACV,OAAO,GACP,aAAa,GACb,WAAW,CAAC;AAElB;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,yBAAyB;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,uGAAuG;IACvG,cAAc,CAAC,EAAE,qBAAqB,CAAC;IAEvC,6DAA6D;IAC7D,MAAM,EAAE,SAAS,CAAC;IAElB,gDAAgD;IAChD,OAAO,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAA;KAAE,KAAK,SAAS,CAAC,CAAC;IAEpG,2DAA2D;IAC3D,SAAS,CAAC,EAAE,KAAK,CAAC,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC;IAE/C,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,kEAAkE;IAClE,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,wCAAwC;IACxC,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IAElC,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,+FAA+F;IAC/F,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,sDAAsD;IACtD,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACvC;;OAEG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAElC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,SAAS,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAEvC;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,mBAAmB,CAAC,EAAE,aAAa,CAAC;IAEpC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,UAAU,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,cAAc,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useElementSize.d.ts","sourceRoot":"","sources":["../../../src/@ui/hooks/useElementSize.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAAqC,MAAM,OAAO,CAAC;AAE/E;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,CAAC,EAAE,cAAc;;;;
|
|
1
|
+
{"version":3,"file":"useElementSize.d.ts","sourceRoot":"","sources":["../../../src/@ui/hooks/useElementSize.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,cAAc,EAAqC,MAAM,OAAO,CAAC;AAE/E;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,WAAW,EAAE,IAAI,CAAC,EAAE,cAAc;;;;EAoC1E"}
|
|
@@ -7,6 +7,7 @@ export function useElementSize(deps) {
|
|
|
7
7
|
const ref = useRef(null);
|
|
8
8
|
const [height, setHeight] = useState(0);
|
|
9
9
|
const [width, setWidth] = useState(0);
|
|
10
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
10
11
|
useLayoutEffect(() => {
|
|
11
12
|
const resizeObserver = new ResizeObserver((entries) => {
|
|
12
13
|
const [entry] = entries;
|
|
@@ -27,7 +28,7 @@ export function useElementSize(deps) {
|
|
|
27
28
|
setWidth(0);
|
|
28
29
|
}
|
|
29
30
|
};
|
|
30
|
-
//
|
|
31
|
+
// oxlint-disable-next-line react-hooks/exhaustive-deps
|
|
31
32
|
}, deps ?? []);
|
|
32
33
|
return {
|
|
33
34
|
ref,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tells whether the text of an element is cut off by its width. Attach the returned ref to the
|
|
3
|
+
* element with `white-space: nowrap` and `overflow: hidden`, and pass the rendered text so a
|
|
4
|
+
* new text in the same element is measured again; the answer also follows size changes.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
export declare function useIsTextTruncated(text: string): {
|
|
9
|
+
ref: (element: HTMLElement | null) => void;
|
|
10
|
+
isTruncated: boolean;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=useIsTextTruncated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useIsTextTruncated.d.ts","sourceRoot":"","sources":["../../../src/@ui/hooks/useIsTextTruncated.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC9C,GAAG,EAAE,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAC;IAC3C,WAAW,EAAE,OAAO,CAAC;CACxB,CAwBA"}
|