@cloud-ru/ds-list 2.3.3 → 2.3.4
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/CHANGELOG.md +6 -0
- package/dist/cjs/components/Lists/List/List.d.ts +10 -9
- package/dist/cjs/components/Lists/List/List.js +2 -1
- package/dist/cjs/components/Lists/ListPrivate/ListPrivate.js +10 -3
- package/dist/esm/components/Lists/List/List.d.ts +6 -5
- package/dist/esm/components/Lists/List/List.js +2 -1
- package/dist/esm/components/Lists/ListPrivate/ListPrivate.js +11 -4
- package/dist/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/components/Lists/List/List.tsx +6 -3
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +14 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## 2.3.4 (2026-10-09)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **FF-9216:** value selection ([818fa8c](https://github.com/cloud-ru-tech/snack-v2/commit/818fa8ca8730ba81e31cd7f0ca2c7493652592e1))
|
|
11
|
+
|
|
6
12
|
## 2.3.3 (2026-10-09)
|
|
7
13
|
|
|
8
14
|
**Note:** Version bump only for package @ds/list
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { KeyboardEvent } from 'react';
|
|
2
|
+
import { Item } from '../../Items';
|
|
2
3
|
/** Список айтемов: base / collapse / group / next-list, с опциональной виртуализацией. */
|
|
3
4
|
export declare const List: import("react").ForwardRefExoticComponent<{
|
|
4
5
|
'data-test-id'?: string;
|
|
5
6
|
} & import("react").AriaAttributes & {
|
|
6
|
-
pinTop?:
|
|
7
|
-
pinBottom?:
|
|
7
|
+
pinTop?: Item[];
|
|
8
|
+
pinBottom?: Item[];
|
|
8
9
|
footer?: import("react").ReactNode;
|
|
9
10
|
header?: import("react").ReactNode;
|
|
10
11
|
headerDivider?: boolean;
|
|
11
12
|
footerDivider?: boolean;
|
|
12
13
|
footerActiveElementsRefs?: import("react").RefObject<HTMLElement>[];
|
|
13
14
|
keyboardNavigationRef?: import("react").RefObject<{
|
|
14
|
-
focusItem(id: import("
|
|
15
|
+
focusItem(id: import("../../Items").ItemId): void;
|
|
15
16
|
}>;
|
|
16
17
|
search?: import("../../..").SearchState;
|
|
17
18
|
tabIndex?: number;
|
|
@@ -24,7 +25,7 @@ export declare const List: import("react").ForwardRefExoticComponent<{
|
|
|
24
25
|
scrollContainerClassName?: string;
|
|
25
26
|
limitedScrollHeight?: boolean;
|
|
26
27
|
} & import("../contexts").SelectionState & Omit<import("../contexts").PublicListContextType, "virtualized"> & import("../../..").ScrollProps & import("../types").EmptyState & {
|
|
27
|
-
items:
|
|
28
|
+
items: Item[];
|
|
28
29
|
virtualized?: boolean;
|
|
29
30
|
} & import("react").RefAttributes<HTMLElement>>;
|
|
30
31
|
/**
|
|
@@ -35,15 +36,15 @@ export declare const List: import("react").ForwardRefExoticComponent<{
|
|
|
35
36
|
export declare const ReorderableList: import("react").ForwardRefExoticComponent<{
|
|
36
37
|
'data-test-id'?: string;
|
|
37
38
|
} & import("react").AriaAttributes & {
|
|
38
|
-
pinTop?:
|
|
39
|
-
pinBottom?:
|
|
39
|
+
pinTop?: Item[];
|
|
40
|
+
pinBottom?: Item[];
|
|
40
41
|
footer?: import("react").ReactNode;
|
|
41
42
|
header?: import("react").ReactNode;
|
|
42
43
|
headerDivider?: boolean;
|
|
43
44
|
footerDivider?: boolean;
|
|
44
45
|
footerActiveElementsRefs?: import("react").RefObject<HTMLElement>[];
|
|
45
46
|
keyboardNavigationRef?: import("react").RefObject<{
|
|
46
|
-
focusItem(id: import("
|
|
47
|
+
focusItem(id: import("../../Items").ItemId): void;
|
|
47
48
|
}>;
|
|
48
49
|
search?: import("../../..").SearchState;
|
|
49
50
|
tabIndex?: number;
|
|
@@ -56,6 +57,6 @@ export declare const ReorderableList: import("react").ForwardRefExoticComponent<
|
|
|
56
57
|
scrollContainerClassName?: string;
|
|
57
58
|
limitedScrollHeight?: boolean;
|
|
58
59
|
} & import("../contexts").SelectionState & Omit<import("../contexts").PublicListContextType, "virtualized"> & import("../../..").ScrollProps & import("../types").EmptyState & {
|
|
59
|
-
items: import("
|
|
60
|
-
onItemsReorder(items: import("
|
|
60
|
+
items: import("../../Items").ReorderItem[];
|
|
61
|
+
onItemsReorder(items: import("../../Items").ReorderItem[]): void;
|
|
61
62
|
} & import("react").RefAttributes<HTMLElement>>;
|
|
@@ -15,7 +15,8 @@ const contexts_1 = require("../contexts");
|
|
|
15
15
|
const hooks_1 = require("../hooks");
|
|
16
16
|
const ListPrivate_1 = require("../ListPrivate");
|
|
17
17
|
const styles_module_scss_1 = __importDefault(require('../styles.module.css'));
|
|
18
|
-
const
|
|
18
|
+
const EMPTY_ITEMS = [];
|
|
19
|
+
const ListImpl = (0, react_1.forwardRef)(({ items: itemsProp = EMPTY_ITEMS, search, pinBottom: pinBottomProp = EMPTY_ITEMS, pinTop: pinTopProp = EMPTY_ITEMS, footerActiveElementsRefs, onKeyDown, tabIndex = 0, className, collapse = {}, selection, contentRender, size = constants_1.DEFAULT_SIZE, marker = true, keyboardNavigationRef, hasListInFocusChain = true, onItemsReorder, ...props }, ref) => {
|
|
19
20
|
const { openCollapseItems, toggleOpenCollapseItem, searchItem, flattenItems, focusFlattenItems, memorizedItems, ids, expandedIds, firstItemId, onDragEnd, sortableIds, } = (0, hooks_1.useListItemsModel)({
|
|
20
21
|
items: itemsProp,
|
|
21
22
|
pinTop: pinTopProp,
|
|
@@ -92,6 +92,7 @@ exports.ListPrivate = (0, react_1.forwardRef)(({ items, pinTop, pinBottom, onKey
|
|
|
92
92
|
// fixed` у `DragOverlay` остаётся относительно вьюпорта (в т.ч. внутри popover-Droplist).
|
|
93
93
|
const portalRoot = (0, portal_context_1.usePortalContext)();
|
|
94
94
|
const innerScrollRef = (0, react_1.useRef)(null);
|
|
95
|
+
const lastCenteredItemRef = (0, react_1.useRef)(null);
|
|
95
96
|
// `@cloud-ru/ds-scroll` (OverlayScrollbars) разрешает свой ref в viewport-элемент только после
|
|
96
97
|
// инициализации инстанса. До этого `innerScrollRef.current` === null, и виртуализатор
|
|
97
98
|
// не получает scroll-элемент → рендерит пустой контейнер. Флаг поднимается из
|
|
@@ -110,11 +111,11 @@ exports.ListPrivate = (0, react_1.forwardRef)(({ items, pinTop, pinBottom, onKey
|
|
|
110
111
|
selectedItemIndex: -1,
|
|
111
112
|
selectedItem: undefined,
|
|
112
113
|
};
|
|
113
|
-
if (!scrollToSelectedItem ||
|
|
114
|
+
if (!scrollToSelectedItem || (0, utils_1.isNil)(value)) {
|
|
114
115
|
return result;
|
|
115
116
|
}
|
|
116
117
|
const selectedItem = isSelectionSingle ? flattenItems[value] : flattenItems[value[0]];
|
|
117
|
-
if (!selectedItem
|
|
118
|
+
if (!selectedItem || (0, utils_1.isNil)(selectedItem.id)) {
|
|
118
119
|
return result;
|
|
119
120
|
}
|
|
120
121
|
const allFocusFlattenItems = Object.values(focusFlattenItems);
|
|
@@ -223,7 +224,8 @@ exports.ListPrivate = (0, react_1.forwardRef)(({ items, pinTop, pinBottom, onKey
|
|
|
223
224
|
// ведут эффекты выше через виртуализатор, поэтому здесь только `!virtualized`. Хелпер тот
|
|
224
225
|
// же `centerItemInScrollContainer` — обычному листу отдельный виртуализатор не нужен.
|
|
225
226
|
(0, react_1.useEffect)(() => {
|
|
226
|
-
if (virtualized || !scroll || !scrollToSelectedItem || !scrollElementReady) {
|
|
227
|
+
if (virtualized || !scroll || !scrollToSelectedItem || !scrollElementReady || !selectedItem) {
|
|
228
|
+
lastCenteredItemRef.current = null;
|
|
227
229
|
return;
|
|
228
230
|
}
|
|
229
231
|
const container = innerScrollRef.current;
|
|
@@ -231,7 +233,12 @@ exports.ListPrivate = (0, react_1.forwardRef)(({ items, pinTop, pinBottom, onKey
|
|
|
231
233
|
if (!container || !item) {
|
|
232
234
|
return;
|
|
233
235
|
}
|
|
236
|
+
if (lastCenteredItemRef.current?.id === selectedItem.originalId &&
|
|
237
|
+
lastCenteredItemRef.current.container === container) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
234
240
|
centerItemInScrollContainer(container, item);
|
|
241
|
+
lastCenteredItemRef.current = { id: selectedItem.originalId, container };
|
|
235
242
|
}, [virtualized, scroll, scrollToSelectedItem, scrollElementReady, selectedItem]);
|
|
236
243
|
const loadingJSX = (0, react_1.useMemo)(() => loading && ((0, jsx_runtime_1.jsx)("div", { role: 'status', tabIndex: -1, className: styles_module_scss_2.default.loader, "data-size": size, "data-no-items": hasNoItems || undefined, "data-test-id": constants_1.TEST_IDS.loader, children: (0, jsx_runtime_1.jsx)(loader_1.Spinner, { size: constants_2.SPINNER_SIZE_MAP[size] }) })), [hasNoItems, loading, size]);
|
|
237
244
|
const content = (0, react_1.useMemo)(() => {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { KeyboardEvent } from 'react';
|
|
2
|
+
import { Item } from '../../Items/index.js';
|
|
2
3
|
/** Список айтемов: base / collapse / group / next-list, с опциональной виртуализацией. */
|
|
3
4
|
export declare const List: import("react").ForwardRefExoticComponent<{
|
|
4
5
|
'data-test-id'?: string;
|
|
5
6
|
} & import("react").AriaAttributes & {
|
|
6
|
-
pinTop?:
|
|
7
|
-
pinBottom?:
|
|
7
|
+
pinTop?: Item[];
|
|
8
|
+
pinBottom?: Item[];
|
|
8
9
|
footer?: import("react").ReactNode;
|
|
9
10
|
header?: import("react").ReactNode;
|
|
10
11
|
headerDivider?: boolean;
|
|
@@ -24,7 +25,7 @@ export declare const List: import("react").ForwardRefExoticComponent<{
|
|
|
24
25
|
scrollContainerClassName?: string;
|
|
25
26
|
limitedScrollHeight?: boolean;
|
|
26
27
|
} & import("..").SelectionState & Omit<import("..").PublicListContextType, "virtualized"> & import("../../..").ScrollProps & import("..").EmptyState & {
|
|
27
|
-
items:
|
|
28
|
+
items: Item[];
|
|
28
29
|
virtualized?: boolean;
|
|
29
30
|
} & import("react").RefAttributes<HTMLElement>>;
|
|
30
31
|
/**
|
|
@@ -35,8 +36,8 @@ export declare const List: import("react").ForwardRefExoticComponent<{
|
|
|
35
36
|
export declare const ReorderableList: import("react").ForwardRefExoticComponent<{
|
|
36
37
|
'data-test-id'?: string;
|
|
37
38
|
} & import("react").AriaAttributes & {
|
|
38
|
-
pinTop?:
|
|
39
|
-
pinBottom?:
|
|
39
|
+
pinTop?: Item[];
|
|
40
|
+
pinBottom?: Item[];
|
|
40
41
|
footer?: import("react").ReactNode;
|
|
41
42
|
header?: import("react").ReactNode;
|
|
42
43
|
headerDivider?: boolean;
|
|
@@ -9,7 +9,8 @@ import { CollapseContext, FocusListContext, NewListContextProvider, SelectionPro
|
|
|
9
9
|
import { useListItemsModel, useNewKeyboardNavigation } from '../hooks.js';
|
|
10
10
|
import { ListPrivate } from '../ListPrivate/index.js';
|
|
11
11
|
import styles from '../styles.module.css';
|
|
12
|
-
const
|
|
12
|
+
const EMPTY_ITEMS = [];
|
|
13
|
+
const ListImpl = forwardRef(({ items: itemsProp = EMPTY_ITEMS, search, pinBottom: pinBottomProp = EMPTY_ITEMS, pinTop: pinTopProp = EMPTY_ITEMS, footerActiveElementsRefs, onKeyDown, tabIndex = 0, className, collapse = {}, selection, contentRender, size = DEFAULT_SIZE, marker = true, keyboardNavigationRef, hasListInFocusChain = true, onItemsReorder, ...props }, ref) => {
|
|
13
14
|
const { openCollapseItems, toggleOpenCollapseItem, searchItem, flattenItems, focusFlattenItems, memorizedItems, ids, expandedIds, firstItemId, onDragEnd, sortableIds, } = useListItemsModel({
|
|
14
15
|
items: itemsProp,
|
|
15
16
|
pinTop: pinTopProp,
|
|
@@ -4,7 +4,7 @@ import { SortableContext, sortableKeyboardCoordinates, verticalListSortingStrate
|
|
|
4
4
|
import { Spinner } from '@cloud-ru/ds-loader';
|
|
5
5
|
import { usePortalContext } from '@cloud-ru/ds-portal-context';
|
|
6
6
|
import { Scroll } from '@cloud-ru/ds-scroll';
|
|
7
|
-
import { extractSupportProps, isBrowser, useLayoutEffect } from '@cloud-ru/ds-utils';
|
|
7
|
+
import { extractSupportProps, isBrowser, isNil, useLayoutEffect } from '@cloud-ru/ds-utils';
|
|
8
8
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
9
9
|
import cn from 'classnames';
|
|
10
10
|
import mergeRefs from 'merge-refs';
|
|
@@ -86,6 +86,7 @@ export const ListPrivate = forwardRef(({ items, pinTop, pinBottom, onKeyDown, on
|
|
|
86
86
|
// fixed` у `DragOverlay` остаётся относительно вьюпорта (в т.ч. внутри popover-Droplist).
|
|
87
87
|
const portalRoot = usePortalContext();
|
|
88
88
|
const innerScrollRef = useRef(null);
|
|
89
|
+
const lastCenteredItemRef = useRef(null);
|
|
89
90
|
// `@cloud-ru/ds-scroll` (OverlayScrollbars) разрешает свой ref в viewport-элемент только после
|
|
90
91
|
// инициализации инстанса. До этого `innerScrollRef.current` === null, и виртуализатор
|
|
91
92
|
// не получает scroll-элемент → рендерит пустой контейнер. Флаг поднимается из
|
|
@@ -104,11 +105,11 @@ export const ListPrivate = forwardRef(({ items, pinTop, pinBottom, onKeyDown, on
|
|
|
104
105
|
selectedItemIndex: -1,
|
|
105
106
|
selectedItem: undefined,
|
|
106
107
|
};
|
|
107
|
-
if (!scrollToSelectedItem ||
|
|
108
|
+
if (!scrollToSelectedItem || isNil(value)) {
|
|
108
109
|
return result;
|
|
109
110
|
}
|
|
110
111
|
const selectedItem = isSelectionSingle ? flattenItems[value] : flattenItems[value[0]];
|
|
111
|
-
if (!selectedItem
|
|
112
|
+
if (!selectedItem || isNil(selectedItem.id)) {
|
|
112
113
|
return result;
|
|
113
114
|
}
|
|
114
115
|
const allFocusFlattenItems = Object.values(focusFlattenItems);
|
|
@@ -217,7 +218,8 @@ export const ListPrivate = forwardRef(({ items, pinTop, pinBottom, onKeyDown, on
|
|
|
217
218
|
// ведут эффекты выше через виртуализатор, поэтому здесь только `!virtualized`. Хелпер тот
|
|
218
219
|
// же `centerItemInScrollContainer` — обычному листу отдельный виртуализатор не нужен.
|
|
219
220
|
useEffect(() => {
|
|
220
|
-
if (virtualized || !scroll || !scrollToSelectedItem || !scrollElementReady) {
|
|
221
|
+
if (virtualized || !scroll || !scrollToSelectedItem || !scrollElementReady || !selectedItem) {
|
|
222
|
+
lastCenteredItemRef.current = null;
|
|
221
223
|
return;
|
|
222
224
|
}
|
|
223
225
|
const container = innerScrollRef.current;
|
|
@@ -225,7 +227,12 @@ export const ListPrivate = forwardRef(({ items, pinTop, pinBottom, onKeyDown, on
|
|
|
225
227
|
if (!container || !item) {
|
|
226
228
|
return;
|
|
227
229
|
}
|
|
230
|
+
if (lastCenteredItemRef.current?.id === selectedItem.originalId &&
|
|
231
|
+
lastCenteredItemRef.current.container === container) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
228
234
|
centerItemInScrollContainer(container, item);
|
|
235
|
+
lastCenteredItemRef.current = { id: selectedItem.originalId, container };
|
|
229
236
|
}, [virtualized, scroll, scrollToSelectedItem, scrollElementReady, selectedItem]);
|
|
230
237
|
const loadingJSX = useMemo(() => loading && (_jsx("div", { role: 'status', tabIndex: -1, className: styles.loader, "data-size": size, "data-no-items": hasNoItems || undefined, "data-test-id": TEST_IDS.loader, children: _jsx(Spinner, { size: SPINNER_SIZE_MAP[size] }) })), [hasNoItems, loading, size]);
|
|
231
238
|
const content = useMemo(() => {
|