@gooddata/sdk-ui-kit 11.43.0-alpha.4 → 11.43.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.
Files changed (37) hide show
  1. package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.d.ts +5 -2
  2. package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.d.ts.map +1 -1
  3. package/esm/@ui/UiGranteeRowControls/UiGranteeRowControls.js +16 -4
  4. package/esm/@ui/UiMenu/UiMenu.d.ts.map +1 -1
  5. package/esm/@ui/UiMenu/UiMenu.js +1 -1
  6. package/esm/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.d.ts +10 -3
  7. package/esm/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.d.ts.map +1 -1
  8. package/esm/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.js +18 -4
  9. package/esm/@ui/UiPermissionMenu/UiPermissionMenu.d.ts +3 -1
  10. package/esm/@ui/UiPermissionMenu/UiPermissionMenu.d.ts.map +1 -1
  11. package/esm/@ui/UiPopover/UiPopover.d.ts.map +1 -1
  12. package/esm/@ui/UiPopover/UiPopover.js +1 -1
  13. package/esm/List/LegacyList.d.ts +1 -1
  14. package/esm/List/LegacyList.d.ts.map +1 -1
  15. package/esm/List/LegacyList.js +103 -55
  16. package/esm/List/List.d.ts +1 -1
  17. package/esm/List/List.d.ts.map +1 -1
  18. package/esm/List/List.js +95 -53
  19. package/esm/Overlay/Overlay.js +2 -2
  20. package/esm/locales.d.ts +3 -0
  21. package/esm/locales.d.ts.map +1 -1
  22. package/esm/locales.js +1 -0
  23. package/esm/sdk-ui-kit.d.ts +19 -8
  24. package/package.json +12 -13
  25. package/src/@ui/UiGranteeRowControls/UiGranteeRowControls.scss +8 -0
  26. package/src/@ui/UiLabelsChecklist/UiLabelsChecklist.scss +11 -0
  27. package/src/@ui/UiMenu/UiMenu.scss +9 -0
  28. package/styles/css/insightList.css +1 -3
  29. package/styles/css/insightList.css.map +1 -1
  30. package/styles/css/list.css +14 -655
  31. package/styles/css/list.css.map +1 -1
  32. package/styles/css/main.css +33 -658
  33. package/styles/css/main.css.map +1 -1
  34. package/styles/css/menu.css +14 -655
  35. package/styles/css/menu.css.map +1 -1
  36. package/styles/scss/insightList.scss +1 -3
  37. package/styles/scss/list.scss +15 -65
@@ -1,3 +1,4 @@
1
+ import { type AccessGranularPermission } from "@gooddata/sdk-model";
1
2
  import { type IUiLabelsChecklistItem } from "../UiLabelsChecklist/UiLabelsChecklist.js";
2
3
  import { type PermissionMenuLevel } from "../UiPermissionMenu/UiPermissionMenu.js";
3
4
  /**
@@ -7,13 +8,15 @@ export interface IUiGranteeRowControlsProps {
7
8
  /** Locked items are always treated as selected. */
8
9
  labels: ReadonlyArray<IUiLabelsChecklistItem>;
9
10
  selectedLabelIds: ReadonlyArray<string>;
10
- permissionLevel: PermissionMenuLevel;
11
+ /** The level to display. `EDIT` renders read-only; VIEW/SHARE are selectable. */
12
+ permissionLevel: AccessGranularPermission;
11
13
  /**
12
14
  * Set only when the grantee inherits a higher permission than `permissionLevel`
13
15
  * (e.g. from a group); drives the warning badge. Undefined when not elevated.
14
16
  */
15
- effectivePermission?: PermissionMenuLevel;
17
+ effectivePermission?: AccessGranularPermission;
16
18
  onLabelsChange: (selectedIds: string[]) => void;
19
+ /** Fires only for selectable rows (VIEW/SHARE); EDIT rows have no level control. */
17
20
  onPermissionChange: (level: PermissionMenuLevel) => void;
18
21
  onTransferOwnership?: () => void;
19
22
  onRemoveAccess?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"UiGranteeRowControls.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiGranteeRowControls/UiGranteeRowControls.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAExF,OAAO,EAAE,KAAK,mBAAmB,EAAoB,MAAM,yCAAyC,CAAC;AAKrG;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACvC,mDAAmD;IACnD,MAAM,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC9C,gBAAgB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,eAAe,EAAE,mBAAmB,CAAC;IACrC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,cAAc,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAChD,kBAAkB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACzD,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,MAAM,EACN,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,UAAU,EACb,EAAE,0BAA0B,2CAgE5B"}
1
+ {"version":3,"file":"UiGranteeRowControls.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiGranteeRowControls/UiGranteeRowControls.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAMpE,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,2CAA2C,CAAC;AAExF,OAAO,EAAE,KAAK,mBAAmB,EAAoB,MAAM,yCAAyC,CAAC;AAKrG;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACvC,mDAAmD;IACnD,MAAM,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC9C,gBAAgB,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,iFAAiF;IACjF,eAAe,EAAE,wBAAwB,CAAC;IAC1C;;;OAGG;IACH,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C,cAAc,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAChD,oFAAoF;IACpF,kBAAkB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACzD,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,qEAAqE;IACrE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,MAAM,EACN,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,UAAU,EACb,EAAE,0BAA0B,2CAmF5B"}
@@ -20,14 +20,26 @@ const { b, e } = bem("gd-ui-kit-grantee-row-controls");
20
20
  export function UiGranteeRowControls({ labels, selectedLabelIds, permissionLevel, effectivePermission, onLabelsChange, onPermissionChange, onTransferOwnership, onRemoveAccess, isDisabled, dataTestId, }) {
21
21
  const intl = useIntl();
22
22
  const hasLabels = labels.length > 0;
23
- const permissionTriggerText = intl.formatMessage(permissionLevel === "SHARE" ? olpPermissionMessages.canViewAndShare : olpPermissionMessages.canView);
24
- // No labels and no transfer nothing in the menu, so drop it.
25
- const hasMoreOptions = hasLabels || !!onTransferOwnership;
23
+ // EDIT is display-only: the dialog can't assign or change it, and offering the
24
+ // VIEW/SHARE menu on an EDIT row would silently downgrade the grant on pick.
25
+ // So an EDIT row shows a static, non-interactive "Can edit" label instead of
26
+ // the permission dropdown.
27
+ const isReadOnlyLevel = permissionLevel === "EDIT";
28
+ const permissionTriggerText = intl.formatMessage(isReadOnlyLevel
29
+ ? olpPermissionMessages.canEdit
30
+ : permissionLevel === "SHARE"
31
+ ? olpPermissionMessages.canViewAndShare
32
+ : olpPermissionMessages.canView);
33
+ // A read-only level has no permission dropdown to host Remove access, so that
34
+ // action moves into the ⋯ menu for those rows (dropdown rows keep it in the menu).
35
+ const removeInMoreOptions = isReadOnlyLevel ? onRemoveAccess : undefined;
36
+ // Nothing to put in the ⋯ menu → drop it.
37
+ const hasMoreOptions = hasLabels || !!onTransferOwnership || !!removeInMoreOptions;
26
38
  // Guard on permissionLevel too, so the badge can't show when the assigned
27
39
  // level already matches the inherited one.
28
40
  const isInheritedHigherPermission = permissionLevel === "VIEW" && effectivePermission === "SHARE";
29
41
  const effectiveTooltip = isInheritedHigherPermission
30
42
  ? intl.formatMessage(olpPermissionMessages.effectivePermissionTooltipShare)
31
43
  : undefined;
32
- return (_jsxs("div", { className: b(), "data-testid": dataTestId, children: [effectiveTooltip ? (_jsx(UiTooltip, { triggerBy: ["hover", "focus"], content: effectiveTooltip, anchor: _jsx("span", { className: e("effective-warning"), role: "img", "aria-label": intl.formatMessage(olpPermissionMessages.effectivePermissionAriaLabel), tabIndex: 0, children: _jsx(UiIcon, { type: "warning", size: 16, color: "warning" }) }) })) : null, _jsx(UiPermissionMenu, { anchor: _jsx(UiButton, { label: permissionTriggerText, size: "small", variant: "dropdownInline", iconAfter: "navigateDown", isDisabled: isDisabled }), selectedLevel: permissionLevel, onPermissionChange: onPermissionChange, onRemoveAccess: onRemoveAccess }), hasMoreOptions ? (_jsx(UiMoreOptionsMenu, { labels: hasLabels ? labels : undefined, selectedLabelIds: selectedLabelIds, onLabelsChange: onLabelsChange, onTransferOwnership: onTransferOwnership, isDisabled: isDisabled })) : null] }));
44
+ return (_jsxs("div", { className: b(), "data-testid": dataTestId, children: [effectiveTooltip ? (_jsx(UiTooltip, { triggerBy: ["hover", "focus"], content: effectiveTooltip, anchor: _jsx("span", { className: e("effective-warning"), role: "img", "aria-label": intl.formatMessage(olpPermissionMessages.effectivePermissionAriaLabel), tabIndex: 0, children: _jsx(UiIcon, { type: "warning", size: 16, color: "warning" }) }) })) : null, permissionLevel === "EDIT" ? (_jsx("span", { className: e("readonly-permission"), children: permissionTriggerText })) : (_jsx(UiPermissionMenu, { anchor: _jsx(UiButton, { label: permissionTriggerText, size: "small", variant: "dropdownInline", iconAfter: "navigateDown", isDisabled: isDisabled }), selectedLevel: permissionLevel, onPermissionChange: onPermissionChange, onRemoveAccess: onRemoveAccess })), hasMoreOptions ? (_jsx(UiMoreOptionsMenu, { labels: hasLabels ? labels : undefined, selectedLabelIds: selectedLabelIds, onLabelsChange: onLabelsChange, onTransferOwnership: onTransferOwnership, onRemoveAccess: removeInMoreOptions, isDisabled: isDisabled })) : null] }));
33
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"UiMenu.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiMenu/UiMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAA8C,MAAM,OAAO,CAAC;AAUvG,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAcrE;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,eAAe,GAAG,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,EAChF,KAAK,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAC1B,SAAS,CA6GX"}
1
+ {"version":3,"file":"UiMenu.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiMenu/UiMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAA8C,MAAM,OAAO,CAAC;AAUvG,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAiBrE;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,eAAe,GAAG,MAAM,EAAE,CAAC,SAAS,MAAM,GAAG,MAAM,EAChF,KAAK,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,GAC1B,SAAS,CA6GX"}
@@ -10,7 +10,7 @@ import { b, e } from "./menuBem.js";
10
10
  function ContentWrapper(props) {
11
11
  return (
12
12
  // autofocus always first element in the custom content for now
13
- _jsx(UiAutofocus, { children: _jsx("div", { onKeyDown: props.keyboardNavigationHandler, children: props.children }) }));
13
+ _jsx(UiAutofocus, { children: _jsx("div", { className: e("content-wrapper"), onKeyDown: props.keyboardNavigationHandler, children: props.children }) }));
14
14
  }
15
15
  /**
16
16
  * An accessible menu component that can be navigated by keyboard.
@@ -10,14 +10,21 @@ export interface IUiMoreOptionsMenuProps {
10
10
  onLabelsChange?: (selectedIds: string[]) => void;
11
11
  /** Omit to hide the row (Transfer ownership is gated until wired). */
12
12
  onTransferOwnership?: () => void;
13
+ /**
14
+ * Omit to hide the row. Used for read-only permission rows (e.g. EDIT), whose
15
+ * level control has no dropdown to host Remove access — so the action lives here.
16
+ */
17
+ onRemoveAccess?: () => void;
13
18
  isDisabled?: boolean;
14
19
  dataTestId?: string;
15
20
  }
16
21
  /**
17
- * Per-grantee "⋯" menu: "Manage labels access" (drills into {@link UiLabelsChecklist})
18
- * and "Transfer ownership". Each row shows only when its data/handler is given.
22
+ * Per-grantee "⋯" menu: "Manage labels access" (drills into {@link UiLabelsChecklist}),
23
+ * "Transfer ownership" and "Remove access". Each row shows only when its data/handler
24
+ * is given. Remove access appears here for read-only permission rows whose level control
25
+ * has no dropdown to host it.
19
26
  *
20
27
  * @internal
21
28
  */
22
- export declare function UiMoreOptionsMenu({ labels, selectedLabelIds, onLabelsChange, onTransferOwnership, isDisabled, dataTestId }: IUiMoreOptionsMenuProps): import("react/jsx-runtime").JSX.Element;
29
+ export declare function UiMoreOptionsMenu({ labels, selectedLabelIds, onLabelsChange, onTransferOwnership, onRemoveAccess, isDisabled, dataTestId }: IUiMoreOptionsMenuProps): import("react/jsx-runtime").JSX.Element;
23
30
  //# sourceMappingURL=UiMoreOptionsMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"UiMoreOptionsMenu.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,sBAAsB,EAAqB,MAAM,2CAA2C,CAAC;AAO3G;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC,sFAAsF;IACtF,MAAM,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC/C,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACjD,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAuCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,UAAU,EACV,UAAU,EACb,EAAE,uBAAuB,2CAwFzB"}
1
+ {"version":3,"file":"UiMoreOptionsMenu.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiMoreOptionsMenu/UiMoreOptionsMenu.tsx"],"names":[],"mappings":"AAUA,OAAO,EAAE,KAAK,sBAAsB,EAAqB,MAAM,2CAA2C,CAAC;AAO3G;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC,sFAAsF;IACtF,MAAM,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,CAAC;IAC/C,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,cAAc,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACjD,sEAAsE;IACtE,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;IACjC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAuCD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,EAC9B,MAAM,EACN,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,UAAU,EACb,EAAE,uBAAuB,2CAmGzB"}
@@ -18,12 +18,14 @@ function LabelsChecklistContent({ onBack, onClose, dataRef }) {
18
18
  return (_jsx(UiLabelsChecklist, { items: data.labels, defaultSelectedIds: data.selectedLabelIds, onApply: (ids) => data.onLabelsChange?.(ids), onBack: onBack, onClose: onClose, dataTestId: data.dataTestId }));
19
19
  }
20
20
  /**
21
- * Per-grantee "⋯" menu: "Manage labels access" (drills into {@link UiLabelsChecklist})
22
- * and "Transfer ownership". Each row shows only when its data/handler is given.
21
+ * Per-grantee "⋯" menu: "Manage labels access" (drills into {@link UiLabelsChecklist}),
22
+ * "Transfer ownership" and "Remove access". Each row shows only when its data/handler
23
+ * is given. Remove access appears here for read-only permission rows whose level control
24
+ * has no dropdown to host it.
23
25
  *
24
26
  * @internal
25
27
  */
26
- export function UiMoreOptionsMenu({ labels, selectedLabelIds, onLabelsChange, onTransferOwnership, isDisabled, dataTestId, }) {
28
+ export function UiMoreOptionsMenu({ labels, selectedLabelIds, onLabelsChange, onTransferOwnership, onRemoveAccess, isDisabled, dataTestId, }) {
27
29
  const intl = useIntl();
28
30
  const menuId = useId();
29
31
  const hasLabels = (labels?.length ?? 0) > 0;
@@ -68,14 +70,26 @@ export function UiMoreOptionsMenu({ labels, selectedLabelIds, onLabelsChange, on
68
70
  data: undefined,
69
71
  });
70
72
  }
73
+ if (onRemoveAccess) {
74
+ result.push({
75
+ type: "interactive",
76
+ id: "remove",
77
+ stringTitle: intl.formatMessage(olpPermissionMessages.removeAccess),
78
+ iconLeft: _jsx(UiIcon, { type: "trash", size: 16, color: "complementary-7" }),
79
+ data: undefined,
80
+ });
81
+ }
71
82
  return result;
72
- }, [hasLabels, onTransferOwnership, intl]);
83
+ }, [hasLabels, onTransferOwnership, onRemoveAccess, intl]);
73
84
  return (_jsx(UiPopover, { anchor: _jsx(UiIconButton, { icon: "ellipsis", variant: "tertiary", size: "large", iconColor: "complementary-6", isDisabled: isDisabled, accessibilityConfig: {
74
85
  ariaLabel: intl.formatMessage(olpPermissionMessages.moreOptionsAriaLabel),
75
86
  } }), anchorAccessibilityConfig: { ariaHaspopup: "menu" }, width: 200, content: ({ onClose }) => (_jsx("div", { className: b(), "data-testid": dataTestId, children: _jsx(UiMenu, { items: items, size: "small", onSelect: (item) => {
76
87
  if (item.id === "transfer") {
77
88
  onTransferOwnership?.();
78
89
  }
90
+ else if (item.id === "remove") {
91
+ onRemoveAccess?.();
92
+ }
79
93
  }, onClose: onClose, ariaAttributes: {
80
94
  id: menuId,
81
95
  "aria-label": intl.formatMessage(olpPermissionMessages.moreOptionsMenuLabel),
@@ -1,6 +1,8 @@
1
1
  import { type ReactElement } from "react";
2
2
  /**
3
- * Permission level offered by the menu.
3
+ * Selectable permission level. The menu and the add-grantee picker only ever
4
+ * assign VIEW or SHARE — EDIT is shown as a read-only row (via the model's
5
+ * `AccessGranularPermission`) and is never selectable from the UI.
4
6
  *
5
7
  * @internal
6
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"UiPermissionMenu.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiPermissionMenu/UiPermissionMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,YAAY,EAAU,MAAM,OAAO,CAAC;AActE;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC,4CAA4C;IAC5C,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;IAC1B,qEAAmE;IACnE,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,oDAAoD;IACpD,kBAAkB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACzD,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,UAAU,EACb,EAAE,sBAAsB,2CAiBxB"}
1
+ {"version":3,"file":"UiPermissionMenu.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiPermissionMenu/UiPermissionMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,YAAY,EAAU,MAAM,OAAO,CAAC;AActE;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEnD;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC,4CAA4C;IAC5C,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;IAC1B,qEAAmE;IACnE,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,oDAAoD;IACpD,kBAAkB,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACzD,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAC5B,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,MAAM,EACN,aAAa,EACb,kBAAkB,EAClB,cAAc,EACd,UAAU,EACb,EAAE,sBAAsB,2CAiBxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"UiPopover.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiPopover/UiPopover.tsx"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,cAAc,EAEnB,KAAK,YAAY,EACjB,KAAK,SAAS,EAGd,KAAK,SAAS,EAIjB,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAM/E,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAqB7D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;IAC1B;;;;;;;OAOG;IACH,yBAAyB,CAAC,EAAE;QACxB,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;QACvE,YAAY,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;KAClD,CAAC;IACF,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,SAAS,CAAC,CAAC;IACrE;;;;;OAKG;IACH,cAAc,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,SAAS,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC;IACnD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAC/C,aAAa,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAChD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;IACjD;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,EACtB,EAAE,EACF,mBAAgD,EAChD,MAAM,EACN,yBAAyB,EACzB,KAAiB,EACjB,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,cAA0B,EAC1B,MAAM,EACN,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAqB,EACrB,cAA4B,EAC5B,gBAAuB,EACvB,qBAA4B,EAC5B,YAAkC,EAClC,eAAuB,EACvB,MAAM,EACN,YAAY,EACZ,MAAM,EACN,OAAO,EACV,EAAE,eAAe,2CA8GjB"}
1
+ {"version":3,"file":"UiPopover.d.ts","sourceRoot":"","sources":["../../../src/@ui/UiPopover/UiPopover.tsx"],"names":[],"mappings":"AAEA,OAAO,EACH,KAAK,cAAc,EAEnB,KAAK,YAAY,EACjB,KAAK,SAAS,EAGd,KAAK,SAAS,EAIjB,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAM/E,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAqB7D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;IAC1B;;;;;;;OAOG;IACH,yBAAyB,CAAC,EAAE;QACxB,YAAY,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;QACvE,YAAY,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;KAClD,CAAC;IACF,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,wBAAwB,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,SAAS,CAAC,CAAC;IACrE;;;;;OAKG;IACH,cAAc,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACpC,MAAM,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,SAAS,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;IACzC;;;;OAIG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC,gBAAgB,CAAC,CAAC;IACnD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAC/C,aAAa,CAAC,EAAE,SAAS,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAChD,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC;IACjD;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,uFAAuF;IACvF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,EACtB,EAAE,EACF,mBAAgD,EAChD,MAAM,EACN,yBAAyB,EACzB,KAAiB,EACjB,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,cAA0B,EAC1B,MAAM,EACN,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAqB,EACrB,cAA4B,EAC5B,gBAAuB,EACvB,qBAA4B,EAC5B,YAAkC,EAClC,eAAuB,EACvB,MAAM,EACN,YAAY,EACZ,MAAM,EACN,OAAO,EACV,EAAE,eAAe,2CA6GjB"}
@@ -54,7 +54,7 @@ export function UiPopover({ id, accessibilityConfig = defaultAccessibilityConfig
54
54
  ? undefined
55
55
  : () => {
56
56
  onClose();
57
- }, children: _jsxs("div", { id: id ?? currentId, tabIndex: 0, className: b(), style: { width: width === "default" ? undefined : width }, children: [title || closeVisible ? (_jsxs("div", { className: e("header"), children: [title ? (typeof title === "string" ? (_jsx("div", { id: titleId, className: e("header-title"), children: title })) : (_jsx("div", { id: titleId, children: title }))) : null, closeVisible ? (_jsx("div", { className: e("header-close"), children: _jsx(UiButton, { label: "", size: "small", variant: "tertiary", iconBefore: "close", accessibilityConfig: {
57
+ }, children: _jsxs("div", { id: id ?? currentId, className: b(), style: { width: width === "default" ? undefined : width }, children: [title || closeVisible ? (_jsxs("div", { className: e("header"), children: [title ? (typeof title === "string" ? (_jsx("div", { id: titleId, className: e("header-title"), children: title })) : (_jsx("div", { id: titleId, children: title }))) : null, closeVisible ? (_jsx("div", { className: e("header-close"), children: _jsx(UiButton, { label: "", size: "small", variant: "tertiary", iconBefore: "close", accessibilityConfig: {
58
58
  ariaLabel: closeText,
59
59
  }, onClick: onClose }) })) : null] })) : null, content ? (_jsx("div", { className: e("content", { padding: contentPadding }), children: typeof content === "function" ? content({ onClose }) : content })) : null, footer ? (_jsx("div", { className: e("footer"), children: typeof footer === "function" ? footer({ onClose }) : footer })) : null] }) }));
60
60
  }, variant: "none", behaviour: "popover", triggerBy: triggerBy, showArrow: false, optimalPlacement: optimalPlacement, arrowPlacement: arrowPlacement, disabled: disabled }));
@@ -27,5 +27,5 @@ export interface ILegacyListState {
27
27
  * @deprecated This component is deprecated use List instead
28
28
  * @internal
29
29
  */
30
- export declare function LegacyList({ className, onScroll, onScrollStart, onSelect, width, height, itemHeight, itemHeightGetter, compensateBorder, scrollToSelected, dataSource, rowItem }: ILegacyListProps): import("react/jsx-runtime").JSX.Element;
30
+ export declare function LegacyList({ className, onScroll, onScrollStart, onSelect, width, height, itemHeight, itemHeightGetter, scrollToSelected, dataSource, rowItem }: ILegacyListProps): import("react/jsx-runtime").JSX.Element;
31
31
  //# sourceMappingURL=LegacyList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"LegacyList.d.ts","sourceRoot":"","sources":["../../src/List/LegacyList.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAA2D,MAAM,OAAO,CAAC;AAKnG,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAehD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE,GAAG,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACvB,SAAc,EACd,QAAmB,EACnB,aAAwB,EACxB,QAAmB,EACnB,KAAW,EACX,MAAY,EACZ,UAAe,EACf,gBAAkD,EAClD,gBAAuB,EACvB,gBAAwB,EACxB,UAAU,EACV,OAAO,EACV,EAAE,gBAAgB,2CA8GlB"}
1
+ {"version":3,"file":"LegacyList.d.ts","sourceRoot":"","sources":["../../src/List/LegacyList.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,YAAY,EAOpB,MAAM,OAAO,CAAC;AAKf,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAUhD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,EAAE,GAAG,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,MAAM,CAAC;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACvB,SAAc,EACd,QAAmB,EACnB,aAAwB,EACxB,QAAmB,EACnB,KAAW,EACX,MAAY,EACZ,UAAe,EACf,gBAAkD,EAClD,gBAAwB,EACxB,UAAU,EACV,OAAO,EACV,EAAE,gBAAgB,2CAuKlB"}
@@ -1,77 +1,125 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- // (C) 2007-2025 GoodData Corporation
3
- import { cloneElement, useCallback, useEffect, useMemo, useState } from "react";
2
+ // (C) 2007-2026 GoodData Corporation
3
+ import { cloneElement, useCallback, useEffect, useMemo, useRef, } from "react";
4
+ import { useVirtualizer } from "@tanstack/react-virtual";
4
5
  import cx from "classnames";
5
- import { Cell, Column, Table } from "fixed-data-table-2";
6
- const BORDER_HEIGHT = 1;
7
6
  // it configures max number of records due to
8
7
  // inefficiency with virtual memory allocation
9
8
  // that causes application crash (TNT-787)
10
9
  const MAX_NUMBER_OF_ROWS = 1000000;
11
- const preventDefault = (e) => e.preventDefault();
12
- function isTouchDevice() {
13
- return "ontouchstart" in document.documentElement;
14
- }
10
+ // delay (ms) after the last scroll event before the scroll-end callback is fired
11
+ const SCROLL_END_DELAY = 100;
15
12
  /**
16
13
  * @deprecated This component is deprecated use List instead
17
14
  * @internal
18
15
  */
19
- export function LegacyList({ className = "", onScroll = () => { }, onScrollStart = () => { }, onSelect = () => { }, width = 200, height = 300, itemHeight = 28, itemHeightGetter = null, compensateBorder = true, scrollToSelected = false, dataSource, rowItem, }) {
20
- const [selected, setSelected] = useState(null);
21
- useEffect(() => {
22
- if (scrollToSelected) {
23
- [...Array(dataSource.rowsCount).keys()].forEach((row) => {
24
- const item = dataSource.getObjectAt(row);
25
- if (item?.selected) {
26
- // Because list items start from 0 we need to add the +1 here
27
- setSelected(row + 1);
28
- }
29
- });
16
+ export function LegacyList({ className = "", onScroll = () => { }, onScrollStart = () => { }, onSelect = () => { }, width = 200, height = 300, itemHeight = 28, itemHeightGetter = null, scrollToSelected = false, dataSource, rowItem, }) {
17
+ const scrollContainerRef = useRef(null);
18
+ const rowsCount = Math.min(dataSource.rowsCount, MAX_NUMBER_OF_ROWS);
19
+ const estimateSize = useCallback(() => (itemHeightGetter ? itemHeightGetter() : itemHeight), [itemHeightGetter, itemHeight]);
20
+ // Report the viewport size to the virtualizer from the known props, falling back from a real
21
+ // measurement when the DOM has no layout (e.g. jsdom in tests). Keeps virtualization correct
22
+ // without depending on layout being available.
23
+ const observeElementRect = useCallback((_instance, cb) => {
24
+ const el = scrollContainerRef.current;
25
+ if (!el) {
26
+ return undefined;
27
+ }
28
+ const report = () => {
29
+ const rect = el.getBoundingClientRect();
30
+ cb({ width: rect.width || width, height: rect.height || height });
31
+ };
32
+ report();
33
+ if (typeof ResizeObserver === "undefined") {
34
+ return undefined;
30
35
  }
36
+ const resizeObserver = new ResizeObserver(report);
37
+ resizeObserver.observe(el);
38
+ return () => resizeObserver.disconnect();
39
+ }, [width, height]);
40
+ const rowVirtualizer = useVirtualizer({
41
+ count: rowsCount,
42
+ getScrollElement: () => scrollContainerRef.current,
43
+ estimateSize,
44
+ observeElementRect,
45
+ overscan: 5,
46
+ });
47
+ const virtualItems = rowVirtualizer.getVirtualItems();
48
+ // index of the currently selected row, used to scroll it into view
49
+ const selectedRowIndex = useMemo(() => {
50
+ if (!scrollToSelected) {
51
+ return undefined;
52
+ }
53
+ for (let row = 0; row < dataSource.rowsCount; row += 1) {
54
+ if (dataSource.getObjectAt(row)?.selected) {
55
+ return row;
56
+ }
57
+ }
58
+ return undefined;
31
59
  }, [scrollToSelected, dataSource]);
32
- const enablePageScrolling = useCallback(() => {
33
- document.body.removeEventListener("wheel", preventDefault);
34
- }, []);
60
+ useEffect(() => {
61
+ if (selectedRowIndex === undefined || rowsCount === 0) {
62
+ return;
63
+ }
64
+ rowVirtualizer.scrollToIndex(Math.min(selectedRowIndex, rowsCount - 1));
65
+ // rowVirtualizer identity is stable; intentionally only react to the selected index changing
66
+ // eslint-disable-next-line react-hooks/exhaustive-deps
67
+ }, [selectedRowIndex, rowsCount]);
68
+ // vertical scroll position is converted to the [first, last] visible item index range,
69
+ // matching the legacy fixed-data-table-based behavior
70
+ const getVisibleScrollRange = useCallback((scrollY) => {
71
+ const rowIndex = Math.floor(scrollY / itemHeight);
72
+ const visibleRange = Math.ceil(height / itemHeight);
73
+ return [rowIndex, rowIndex + visibleRange];
74
+ }, [height, itemHeight]);
75
+ const isScrollingRef = useRef(false);
76
+ const scrollEndTimerRef = useRef(undefined);
77
+ const handleScroll = useCallback((event) => {
78
+ const scrollY = event.currentTarget.scrollTop;
79
+ const [startIndex, endIndex] = getVisibleScrollRange(scrollY);
80
+ if (!isScrollingRef.current) {
81
+ isScrollingRef.current = true;
82
+ onScrollStart(startIndex, endIndex);
83
+ }
84
+ if (scrollEndTimerRef.current) {
85
+ clearTimeout(scrollEndTimerRef.current);
86
+ }
87
+ scrollEndTimerRef.current = setTimeout(() => {
88
+ isScrollingRef.current = false;
89
+ onScroll(startIndex, endIndex);
90
+ }, SCROLL_END_DELAY);
91
+ }, [getVisibleScrollRange, onScrollStart, onScroll]);
92
+ // clear any pending scroll-end timer on unmount so callbacks don't fire on an unmounted tree
35
93
  useEffect(() => {
36
94
  return () => {
37
- enablePageScrolling();
95
+ if (scrollEndTimerRef.current) {
96
+ clearTimeout(scrollEndTimerRef.current);
97
+ }
38
98
  };
39
- }, [enablePageScrolling]);
40
- const onSelectHandler = useCallback((_event, rowIndex) => {
99
+ }, []);
100
+ const handleSelect = useCallback((rowIndex) => {
41
101
  const item = dataSource.getObjectAt(rowIndex);
42
102
  if (item) {
43
103
  onSelect(item);
44
104
  }
45
105
  }, [dataSource, onSelect]);
46
- const onScrollHandler = useCallback((method, scrollY) => {
47
- if (method) {
48
- // vertical scroll position returned by fixed-data-table is converted to index of first visible item
49
- const rowIndex = Math.floor(scrollY / itemHeight);
50
- const visibleRange = Math.ceil(height / itemHeight);
51
- method(rowIndex, rowIndex + visibleRange);
52
- }
53
- }, [height, itemHeight]);
54
- const onScrollStartHandler = useCallback((_scrollX, scrollY) => {
55
- onScrollHandler(onScrollStart, scrollY);
56
- }, [onScrollHandler, onScrollStart]);
57
- const onScrollEndHandler = useCallback((_scrollX, scrollY) => {
58
- onScrollHandler(onScroll, scrollY);
59
- }, [onScrollHandler, onScroll]);
60
106
  const classNames = useMemo(() => cx("gd-infinite-list", className), [className]);
61
- const disablePageScrolling = useCallback(() => {
62
- document.body.addEventListener("wheel", preventDefault, { passive: false });
63
- }, []);
64
- const renderCell = useCallback((cellProps) => {
65
- const item = dataSource.getObjectAt(cellProps.rowIndex);
66
- const itemElement = cloneElement(rowItem, {
67
- ...(item ? { item } : {}),
68
- width: width,
69
- isFirst: cellProps.rowIndex === 0,
70
- isLast: cellProps.rowIndex === dataSource.rowsCount - 1,
71
- });
72
- return _jsx(Cell, { ...cellProps, children: itemElement });
73
- }, [dataSource, rowItem, width]);
74
- // compensates for https://github.com/facebook/fixed-data-table/blob/5373535d98b08b270edd84d7ce12833a4478c6b6/src/FixedDataTableNew.js#L872
75
- const compensatedHeight = useMemo(() => (compensateBorder ? height + BORDER_HEIGHT * 2 : height), [compensateBorder, height]);
76
- return (_jsx("div", { className: classNames, onMouseOver: disablePageScrolling, onMouseOut: enablePageScrolling, children: _jsx(Table, { width: width, height: compensatedHeight, rowHeight: itemHeight, rowHeightGetter: itemHeightGetter, headerHeight: 0, rowsCount: Math.min(dataSource.rowsCount, MAX_NUMBER_OF_ROWS), onRowClick: onSelectHandler, onScrollStart: onScrollStartHandler, onScrollEnd: onScrollEndHandler, touchScrollEnabled: isTouchDevice(), scrollToRow: selected, children: _jsx(Column, { flexGrow: 1, width: 1, cell: renderCell }) }) }));
107
+ const containerStyle = useMemo(() => ({ width, height }), [width, height]);
108
+ return (_jsx("div", { className: classNames, style: { width }, children: _jsx("div", { ref: scrollContainerRef, className: "gd-infinite-list-scroll-container", style: containerStyle, onScroll: handleScroll, children: _jsx("div", { className: "gd-infinite-list-sizer", style: { height: rowVirtualizer.getTotalSize(), width: "100%", position: "relative" }, children: virtualItems.map((virtualRow) => {
109
+ const item = dataSource.getObjectAt(virtualRow.index);
110
+ const itemElement = cloneElement(rowItem, {
111
+ ...(item ? { item } : {}),
112
+ width,
113
+ isFirst: virtualRow.index === 0,
114
+ isLast: virtualRow.index === dataSource.rowsCount - 1,
115
+ });
116
+ return (_jsx("div", { className: "gd-infinite-list-item", style: {
117
+ position: "absolute",
118
+ top: 0,
119
+ left: 0,
120
+ width: "100%",
121
+ height: virtualRow.size,
122
+ transform: `translateY(${virtualRow.start}px)`,
123
+ }, onClick: () => handleSelect(virtualRow.index), children: itemElement }, virtualRow.key));
124
+ }) }) }) }));
77
125
  }
@@ -44,5 +44,5 @@ export type ScrollCallback = (visibleRowsStartIndex: number, visibleRowsEndIndex
44
44
  /**
45
45
  * @internal
46
46
  */
47
- export declare function List<T>({ id, className, compensateBorder, width, height, maxHeight, items, itemsCount, itemHeight, itemHeightGetter, maxVisibleItemsCount, renderItem, onScrollStart, onScrollEnd, scrollToItem, scrollDirection, accessibilityConfig }: IListProps<T>): ReactElement;
47
+ export declare function List<T>({ id, className, width, height, maxHeight, items, itemsCount, itemHeight, itemHeightGetter, maxVisibleItemsCount, renderItem, onScrollStart, onScrollEnd, scrollToItem, scrollDirection, accessibilityConfig }: IListProps<T>): ReactElement;
48
48
  //# sourceMappingURL=List.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"List.d.ts","sourceRoot":"","sources":["../../src/List/List.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,YAAY,EAAmC,MAAM,OAAO,CAAC;AAM3E,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAS5E,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,UAAU,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC;IAC7D,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAEtC,YAAY,CAAC,EAAE,CAAC,CAAC;IACjB,eAAe,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IACzB,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,cAAc,CAAC;IAE7B,mBAAmB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,gBAAgB,GAAG,MAAM,CAAC,CAAC;CACnF;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,qBAAqB,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,KAAK,IAAI,CAAC;AAElG;;GAEG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,EACpB,EAAE,EACF,SAAc,EACd,gBAAuB,EAEvB,KAAW,EACX,MAAM,EACN,SAAS,EAET,KAAU,EACV,UAAyB,EACzB,UAAgC,EAChC,gBAA+D,EAC/D,oBAA8C,EAC9C,UAAU,EAEV,aAAa,EACb,WAAW,EAEX,YAAY,EACZ,eAAe,EAEf,mBAAmB,EACtB,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,YAAY,CAmH9B"}
1
+ {"version":3,"file":"List.d.ts","sourceRoot":"","sources":["../../src/List/List.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEH,KAAK,YAAY,EAMpB,MAAM,OAAO,CAAC;AAKf,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAU5E,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAC1C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,UAAU,EAAE,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC,KAAK,YAAY,CAAC;IAC7D,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAEtC,YAAY,CAAC,EAAE,CAAC,CAAC;IACjB,eAAe,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IACzB,aAAa,CAAC,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,cAAc,CAAC;IAE7B,mBAAmB,CAAC,EAAE,IAAI,CAAC,wBAAwB,EAAE,gBAAgB,GAAG,MAAM,CAAC,CAAC;CACnF;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,qBAAqB,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,KAAK,IAAI,CAAC;AAElG;;GAEG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,EACpB,EAAE,EACF,SAAc,EAEd,KAAW,EACX,MAAM,EACN,SAAS,EAET,KAAU,EACV,UAAyB,EACzB,UAAgC,EAChC,gBAA+D,EAC/D,oBAA8C,EAC9C,UAAU,EAEV,aAAa,EACb,WAAW,EAEX,YAAY,EACZ,eAAe,EAEf,mBAAmB,EACtB,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,YAAY,CA+K9B"}
package/esm/List/List.js CHANGED
@@ -1,87 +1,129 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  // (C) 2007-2026 GoodData Corporation
3
- import { useCallback, useEffect, useMemo } from "react";
3
+ import { useCallback, useEffect, useMemo, useRef, } from "react";
4
+ import { useVirtualizer } from "@tanstack/react-virtual";
4
5
  import cx from "classnames";
5
- import { Cell, Column, Table } from "fixed-data-table-2";
6
- import { DROPDOWN_BODY_CLASS } from "../Dropdown/Dropdown.js";
7
6
  // it configures max number of records due to
8
7
  // inefficiency with virtual memory allocation
9
8
  // that causes application crash (TNT-787)
10
9
  const MAX_NUMBER_OF_ROWS = 1000000;
11
- const BORDER_HEIGHT = 1;
12
10
  const HALF_ROW = 0.5;
11
+ // delay (ms) after the last scroll event before onScrollEnd is fired
12
+ const SCROLL_END_DELAY = 100;
13
13
  export const MAX_VISIBLE_ITEMS_COUNT = 10;
14
14
  export const DEFAULT_ITEM_HEIGHT = 28;
15
15
  /**
16
16
  * @internal
17
17
  */
18
- export function List({ id, className = "", compensateBorder = true, width = 200, height, maxHeight, items = [], itemsCount = items.length, itemHeight = DEFAULT_ITEM_HEIGHT, itemHeightGetter = null, maxVisibleItemsCount = MAX_VISIBLE_ITEMS_COUNT, renderItem, onScrollStart, onScrollEnd, scrollToItem, scrollDirection, accessibilityConfig, }) {
18
+ export function List({ id, className = "", width = 200, height, maxHeight, items = [], itemsCount = items.length, itemHeight = DEFAULT_ITEM_HEIGHT, itemHeightGetter = null, maxVisibleItemsCount = MAX_VISIBLE_ITEMS_COUNT, renderItem, onScrollStart, onScrollEnd, scrollToItem, scrollDirection, accessibilityConfig, }) {
19
+ const scrollContainerRef = useRef(null);
20
+ const rowsCount = Math.min(itemsCount, MAX_NUMBER_OF_ROWS);
21
+ // when there are more items than fit, show half of the next row to hint scrollability
19
22
  const currentItemsCount = itemsCount > maxVisibleItemsCount ? maxVisibleItemsCount + HALF_ROW : itemsCount;
20
23
  const listHeight = height || currentItemsCount * itemHeight;
21
- const listHeightWithBorder = compensateBorder ? listHeight + BORDER_HEIGHT * 2 : listHeight;
24
+ // the visible viewport height; the list is prop-sized so this is deterministic
25
+ const viewportHeight = maxHeight ? Math.min(maxHeight, listHeight) : listHeight;
26
+ const estimateSize = useCallback((index) => (itemHeightGetter ? itemHeightGetter(index) : itemHeight), [itemHeightGetter, itemHeight]);
27
+ // Report the viewport size to the virtualizer from the known props, falling back from a real
28
+ // measurement when the DOM has no layout (e.g. jsdom in tests). Keeps virtualization correct
29
+ // without depending on layout being available.
30
+ const observeElementRect = useCallback((_instance, cb) => {
31
+ const el = scrollContainerRef.current;
32
+ if (!el) {
33
+ return undefined;
34
+ }
35
+ const report = () => {
36
+ const rect = el.getBoundingClientRect();
37
+ cb({ width: rect.width || width, height: rect.height || viewportHeight });
38
+ };
39
+ report();
40
+ if (typeof ResizeObserver === "undefined") {
41
+ return undefined;
42
+ }
43
+ const resizeObserver = new ResizeObserver(report);
44
+ resizeObserver.observe(el);
45
+ return () => resizeObserver.disconnect();
46
+ }, [width, viewportHeight]);
47
+ const rowVirtualizer = useVirtualizer({
48
+ count: rowsCount,
49
+ getScrollElement: () => scrollContainerRef.current,
50
+ estimateSize,
51
+ observeElementRect,
52
+ overscan: 5,
53
+ });
54
+ const virtualItems = rowVirtualizer.getVirtualItems();
55
+ // scroll the requested item into view (preserves the legacy +scrollDirection offset)
22
56
  const scrollToItemRowIndex = useMemo(() => {
23
57
  if (!scrollToItem) {
24
58
  return undefined;
25
59
  }
26
- return items.indexOf(scrollToItem) + (scrollDirection ?? 1);
60
+ const index = items.indexOf(scrollToItem);
61
+ if (index < 0) {
62
+ return undefined;
63
+ }
64
+ return index + (scrollDirection ?? 1);
27
65
  }, [items, scrollToItem, scrollDirection]);
66
+ useEffect(() => {
67
+ if (scrollToItemRowIndex === undefined || rowsCount === 0) {
68
+ return;
69
+ }
70
+ rowVirtualizer.scrollToIndex(Math.min(Math.max(scrollToItemRowIndex, 0), rowsCount - 1));
71
+ // rowVirtualizer identity is stable; intentionally only react to the target index changing
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, [scrollToItemRowIndex, rowsCount]);
74
+ // converts vertical scroll position to the [first, last] visible item index range,
75
+ // matching the legacy fixed-data-table-based behavior consumers (e.g. AsyncList) depend on
28
76
  const getVisibleScrollRange = useCallback((scrollY) => {
29
77
  const rowIndex = Math.floor(scrollY / itemHeight);
30
78
  const visibleRange = Math.ceil(listHeight / itemHeight);
31
79
  return [rowIndex, rowIndex + visibleRange];
32
80
  }, [itemHeight, listHeight]);
33
- const handleScrollStart = useCallback((_, y) => {
34
- if (onScrollStart) {
35
- const [startIndex, endIndex] = getVisibleScrollRange(y);
36
- onScrollStart(startIndex, endIndex);
81
+ const isScrollingRef = useRef(false);
82
+ const scrollEndTimerRef = useRef(undefined);
83
+ const handleScroll = useCallback((event) => {
84
+ const scrollY = event.currentTarget.scrollTop;
85
+ const [startIndex, endIndex] = getVisibleScrollRange(scrollY);
86
+ if (!isScrollingRef.current) {
87
+ isScrollingRef.current = true;
88
+ onScrollStart?.(startIndex, endIndex);
37
89
  }
38
- }, [onScrollStart, getVisibleScrollRange]);
39
- const handleScrollEnd = useCallback((_, y) => {
40
- if (onScrollEnd) {
41
- const [startIndex, endIndex] = getVisibleScrollRange(y);
42
- onScrollEnd(startIndex, endIndex);
90
+ if (scrollEndTimerRef.current) {
91
+ clearTimeout(scrollEndTimerRef.current);
43
92
  }
44
- }, [onScrollEnd, getVisibleScrollRange]);
93
+ scrollEndTimerRef.current = setTimeout(() => {
94
+ isScrollingRef.current = false;
95
+ onScrollEnd?.(startIndex, endIndex);
96
+ }, SCROLL_END_DELAY);
97
+ }, [getVisibleScrollRange, onScrollStart, onScrollEnd]);
98
+ // clear any pending scroll-end timer on unmount so callbacks don't fire on an unmounted tree
45
99
  useEffect(() => {
46
100
  return () => {
47
- enablePageScrolling();
101
+ if (scrollEndTimerRef.current) {
102
+ clearTimeout(scrollEndTimerRef.current);
103
+ }
48
104
  };
49
105
  }, []);
50
- const styles = useMemo(() => {
51
- return {
52
- width,
53
- };
54
- }, [width]);
55
- return (_jsx("div", { id: id, "data-testid": "gd-list", className: cx("gd-list gd-infinite-list", className), style: styles, onMouseOver: disablePageScrolling, onMouseOut: enablePageScrolling, role: accessibilityConfig?.role, "aria-labelledby": accessibilityConfig?.ariaLabelledBy, children: _jsx(Table, { width: width,
56
- // compensates for https://github.com/facebook/fixed-data-table/blob/5373535d98b08b270edd84d7ce12833a4478c6b6/src/FixedDataTableNew.react.js#L872
57
- height: maxHeight ? undefined : listHeightWithBorder, maxHeight: maxHeight, headerHeight: 0, rowHeight: itemHeight, rowHeightGetter: itemHeightGetter, rowsCount: Math.min(itemsCount, MAX_NUMBER_OF_ROWS), onScrollStart: handleScrollStart, onScrollEnd: handleScrollEnd, scrollToRow: scrollToItemRowIndex, touchScrollEnabled: isTouchDevice(), keyboardScrollEnabled: true, children: _jsx(Column, { flexGrow: 1, width: 1, cell: ({ columnKey, height, width, rowIndex, }) => {
58
- const item = items[rowIndex];
59
- return (_jsx(Cell, { width: width, height: height, rowIndex: rowIndex, columnKey: columnKey, children: renderItem({
60
- rowIndex,
106
+ const containerStyle = useMemo(() => ({
107
+ width,
108
+ height: maxHeight ? undefined : listHeight,
109
+ maxHeight,
110
+ }), [width, maxHeight, listHeight]);
111
+ return (_jsx("div", { id: id, "data-testid": "gd-list", className: cx("gd-list gd-infinite-list", className), style: { width }, role: accessibilityConfig?.role, "aria-labelledby": accessibilityConfig?.ariaLabelledBy, children: _jsx("div", { ref: scrollContainerRef, className: "gd-infinite-list-scroll-container", style: containerStyle, onScroll: handleScroll, children: _jsx("div", { className: "gd-infinite-list-sizer", style: { height: rowVirtualizer.getTotalSize(), width: "100%", position: "relative" }, children: virtualItems.map((virtualRow) => {
112
+ const item = items[virtualRow.index];
113
+ return (_jsx("div", { className: "gd-infinite-list-item", style: {
114
+ position: "absolute",
115
+ top: 0,
116
+ left: 0,
117
+ width: "100%",
118
+ height: virtualRow.size,
119
+ transform: `translateY(${virtualRow.start}px)`,
120
+ }, children: renderItem({
121
+ rowIndex: virtualRow.index,
61
122
  item,
62
123
  width,
63
- height,
64
- isFirst: rowIndex === 0,
65
- isLast: rowIndex === itemsCount - 1,
66
- }) }));
67
- } }) }) }));
68
- }
69
- function preventDefault(e) {
70
- const wheelEvent = e;
71
- const target = wheelEvent.target;
72
- const isDropdownBodyScroll = target?.closest?.(`.${DROPDOWN_BODY_CLASS}`);
73
- if (isDropdownBodyScroll) {
74
- // allow to scroll another lists placed in dropdowns (e.g. in Filter Groups)
75
- return;
76
- }
77
- e.preventDefault();
78
- }
79
- function isTouchDevice() {
80
- return "ontouchstart" in document.documentElement;
81
- }
82
- function disablePageScrolling() {
83
- document.body.addEventListener("wheel", preventDefault, { passive: false });
84
- }
85
- function enablePageScrolling() {
86
- document.body.removeEventListener("wheel", preventDefault);
124
+ height: virtualRow.size,
125
+ isFirst: virtualRow.index === 0,
126
+ isLast: virtualRow.index === itemsCount - 1,
127
+ }) }, virtualRow.key));
128
+ }) }) }) }));
87
129
  }