@snack-uikit/list 0.22.3-preview-bb2cdfb8.0 → 0.23.0
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 +22 -0
- package/README.md +1 -0
- package/dist/cjs/components/Lists/Droplist/DropList.d.ts +1 -1
- package/dist/cjs/components/Lists/Droplist/DropList.js +4 -2
- package/dist/cjs/components/Lists/ListPrivate/ListPrivate.d.ts +1 -0
- package/dist/cjs/components/Lists/ListPrivate/ListPrivate.js +51 -12
- package/dist/cjs/components/Lists/ListPrivate/constants.d.ts +4 -0
- package/dist/cjs/components/Lists/ListPrivate/constants.js +14 -0
- package/dist/cjs/components/Lists/ListPrivate/styles.module.css +12 -0
- package/dist/cjs/components/Lists/types.d.ts +5 -0
- package/dist/esm/components/Lists/Droplist/DropList.d.ts +1 -1
- package/dist/esm/components/Lists/Droplist/DropList.js +2 -2
- package/dist/esm/components/Lists/ListPrivate/ListPrivate.d.ts +1 -0
- package/dist/esm/components/Lists/ListPrivate/ListPrivate.js +36 -4
- package/dist/esm/components/Lists/ListPrivate/constants.d.ts +4 -0
- package/dist/esm/components/Lists/ListPrivate/constants.js +8 -0
- package/dist/esm/components/Lists/ListPrivate/styles.module.css +12 -0
- package/dist/esm/components/Lists/types.d.ts +5 -0
- package/package.json +6 -5
- package/src/components/Lists/Droplist/DropList.tsx +2 -0
- package/src/components/Lists/ListPrivate/ListPrivate.tsx +56 -4
- package/src/components/Lists/ListPrivate/constants.ts +9 -0
- package/src/components/Lists/ListPrivate/styles.module.scss +12 -0
- package/src/components/Lists/types.ts +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
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
|
+
# 0.23.0 (2025-01-22)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **CORESERV-5847:** add optional virtualization to list ([6a05691](https://github.com/cloud-ru-tech/snack-uikit/commit/6a056917c7cd8b56fa432bf4edc804c13feb0a0a))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## 0.22.3 (2025-01-20)
|
|
18
|
+
|
|
19
|
+
### Only dependencies have been changed
|
|
20
|
+
* [@snack-uikit/info-block@0.6.8](https://github.com/cloud-ru-tech/snack-uikit/blob/master/packages/info-block/CHANGELOG.md)
|
|
21
|
+
* [@snack-uikit/toggles@0.13.4](https://github.com/cloud-ru-tech/snack-uikit/blob/master/packages/toggles/CHANGELOG.md)
|
|
22
|
+
* [@snack-uikit/truncate-string@0.6.5](https://github.com/cloud-ru-tech/snack-uikit/blob/master/packages/truncate-string/CHANGELOG.md)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## 0.22.2 (2024-12-26)
|
|
7
29
|
|
|
8
30
|
|
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
| triggerElemRef | `RefObject<HTMLElement>` | - | Ссылка на элемент-триггер для дроплиста |
|
|
61
61
|
| listRef | `RefObject<HTMLElement>` | - | Ссылка на элемент выпадающего списка |
|
|
62
62
|
| closeDroplistOnItemClick | `boolean` | - | Закрывать выпадающий список после клика на базовый айтем. Работает в режимах selection: 'none' \| 'single' |
|
|
63
|
+
| virtualized | `boolean` | - | Включить виртуализацию на компоненты списка. Рекомендуется если у вас от 1к элементов списка |
|
|
63
64
|
| triggerClassName | `string` | - | CSS-класс триггера |
|
|
64
65
|
| open | `boolean` | - | Управляет состоянием показан/не показан. |
|
|
65
66
|
| onOpenChange | `(isOpen: boolean) => void` | - | Колбек отображения компонента. Срабатывает при изменении состояния open. |
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { DroplistProps } from '../types';
|
|
2
|
-
export declare function Droplist({ items: itemsProp, search, pinBottom: pinBottomProp, pinTop: pinTopProp, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse, triggerClassName, selection, contentRender, size, marker, closeDroplistOnItemClick, className, listRef: listRefProp, untouchableScrollbars, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function Droplist({ items: itemsProp, search, pinBottom: pinBottomProp, pinTop: pinTopProp, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse, triggerClassName, selection, contentRender, size, marker, closeDroplistOnItemClick, className, listRef: listRefProp, untouchableScrollbars, virtualized, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -53,9 +53,10 @@ function Droplist(_a) {
|
|
|
53
53
|
closeDroplistOnItemClick = false,
|
|
54
54
|
className,
|
|
55
55
|
listRef: listRefProp,
|
|
56
|
-
untouchableScrollbars = false
|
|
56
|
+
untouchableScrollbars = false,
|
|
57
|
+
virtualized = false
|
|
57
58
|
} = _a,
|
|
58
|
-
props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange", "collapse", "triggerClassName", "selection", "contentRender", "size", "marker", "closeDroplistOnItemClick", "className", "listRef", "untouchableScrollbars"]);
|
|
59
|
+
props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange", "collapse", "triggerClassName", "selection", "contentRender", "size", "marker", "closeDroplistOnItemClick", "className", "listRef", "untouchableScrollbars", "virtualized"]);
|
|
59
60
|
const hasSearch = (0, react_1.useMemo)(() => Boolean(search), [search]);
|
|
60
61
|
const [openCollapseItems = [], setOpenCollapsedItems] = (0, utils_1.useValueControl)(collapse);
|
|
61
62
|
const toggleOpenCollapseItem = (0, react_1.useCallback)(id => setOpenCollapsedItems(items => (items === null || items === void 0 ? void 0 : items.includes(id)) ? items.filter(item => item !== id) : (items !== null && items !== void 0 ? items : []).concat([id])), [setOpenCollapsedItems]);
|
|
@@ -237,6 +238,7 @@ function Droplist(_a) {
|
|
|
237
238
|
items: memorizedItems.items.focusCloseChildIds,
|
|
238
239
|
pinTop: memorizedItems.pinTop.focusCloseChildIds,
|
|
239
240
|
pinBottom: memorizedItems.pinBottom.focusCloseChildIds,
|
|
241
|
+
virtualized: virtualized,
|
|
240
242
|
onKeyDown: handleListKeyDown,
|
|
241
243
|
searchItem: searchItem,
|
|
242
244
|
tabIndex: 0,
|
|
@@ -2,6 +2,7 @@ export declare const ListPrivate: import("react").ForwardRefExoticComponent<Omit
|
|
|
2
2
|
nested?: boolean;
|
|
3
3
|
active?: boolean;
|
|
4
4
|
tabIndex?: number;
|
|
5
|
+
virtualized?: boolean;
|
|
5
6
|
onFocus?(e: import("react").FocusEvent<HTMLElement>): void;
|
|
6
7
|
onBlur?(e: import("react").FocusEvent<HTMLElement>): void;
|
|
7
8
|
onKeyDown?(e: import("react").KeyboardEvent<HTMLElement>): void;
|
|
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
18
18
|
});
|
|
19
19
|
exports.ListPrivate = void 0;
|
|
20
20
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
21
|
+
const react_virtual_1 = require("@tanstack/react-virtual");
|
|
21
22
|
const classnames_1 = __importDefault(require("classnames"));
|
|
22
23
|
const react_1 = require("react");
|
|
23
24
|
const loaders_1 = require("@snack-uikit/loaders");
|
|
@@ -27,6 +28,7 @@ const helperComponents_1 = require("../../../helperComponents");
|
|
|
27
28
|
const Items_1 = require("../../Items");
|
|
28
29
|
const contexts_1 = require("../contexts");
|
|
29
30
|
const styles_module_scss_1 = __importDefault(require('../styles.module.css'));
|
|
31
|
+
const constants_1 = require("./constants");
|
|
30
32
|
const styles_module_scss_2 = __importDefault(require('./styles.module.css'));
|
|
31
33
|
exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
|
|
32
34
|
var {
|
|
@@ -56,9 +58,10 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
56
58
|
dataError,
|
|
57
59
|
dataFiltered,
|
|
58
60
|
scrollToSelectedItem = false,
|
|
61
|
+
virtualized = false,
|
|
59
62
|
scrollContainerClassName
|
|
60
63
|
} = _a,
|
|
61
|
-
props = __rest(_a, ["items", "pinTop", "pinBottom", "onKeyDown", "onBlur", "onFocus", "tabIndex", "active", "scroll", "nested", "search", "searchItem", "scrollRef", "scrollContainerRef", "onScroll", "footer", "loading", "limitedScrollHeight", "untouchableScrollbars", "className", "noDataState", "noResultsState", "errorDataState", "dataError", "dataFiltered", "scrollToSelectedItem", "scrollContainerClassName"]);
|
|
64
|
+
props = __rest(_a, ["items", "pinTop", "pinBottom", "onKeyDown", "onBlur", "onFocus", "tabIndex", "active", "scroll", "nested", "search", "searchItem", "scrollRef", "scrollContainerRef", "onScroll", "footer", "loading", "limitedScrollHeight", "untouchableScrollbars", "className", "noDataState", "noResultsState", "errorDataState", "dataError", "dataFiltered", "scrollToSelectedItem", "virtualized", "scrollContainerClassName"]);
|
|
62
65
|
const {
|
|
63
66
|
size = 's',
|
|
64
67
|
flattenItems,
|
|
@@ -68,6 +71,7 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
68
71
|
value,
|
|
69
72
|
isSelectionSingle
|
|
70
73
|
} = (0, contexts_1.useSelectionContext)();
|
|
74
|
+
const innerScrollRef = (0, react_1.useRef)(null);
|
|
71
75
|
const itemsJSX = (0, Items_1.useRenderItems)(items);
|
|
72
76
|
const itemsPinTopJSX = (0, Items_1.useRenderItems)(pinTop);
|
|
73
77
|
const itemsPinBottomJSX = (0, Items_1.useRenderItems)(pinBottom);
|
|
@@ -77,6 +81,16 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
77
81
|
errorDataState
|
|
78
82
|
});
|
|
79
83
|
const hasNoItems = items.length === 0;
|
|
84
|
+
const virtualizer = (0, react_virtual_1.useVirtualizer)({
|
|
85
|
+
count: itemsJSX.length,
|
|
86
|
+
getScrollElement: () => scroll ? innerScrollRef.current : null,
|
|
87
|
+
estimateSize: () => constants_1.ALL_SIZES[size],
|
|
88
|
+
enabled: virtualized
|
|
89
|
+
});
|
|
90
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
91
|
+
(0, react_1.useEffect)(() => {
|
|
92
|
+
virtualizer.measure();
|
|
93
|
+
}, [virtualizer]);
|
|
80
94
|
const loadingJSX = (0, react_1.useMemo)(() => loading && (0, jsx_runtime_1.jsx)("div", {
|
|
81
95
|
role: 'spinbutton',
|
|
82
96
|
tabIndex: -1,
|
|
@@ -88,16 +102,38 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
88
102
|
size: size === 'l' ? 's' : 'xs'
|
|
89
103
|
})
|
|
90
104
|
}), [hasNoItems, loading, size]);
|
|
91
|
-
const content = (0, react_1.useMemo)(() =>
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
105
|
+
const content = (0, react_1.useMemo)(() => {
|
|
106
|
+
var _a, _b;
|
|
107
|
+
return (0, jsx_runtime_1.jsxs)("div", {
|
|
108
|
+
className: styles_module_scss_2.default.content,
|
|
109
|
+
children: [virtualized ? (0, jsx_runtime_1.jsx)("div", {
|
|
110
|
+
className: styles_module_scss_2.default.virtualizedContainer,
|
|
111
|
+
style: {
|
|
112
|
+
height: virtualizer.getTotalSize()
|
|
113
|
+
},
|
|
114
|
+
tabIndex: -1,
|
|
115
|
+
children: (0, jsx_runtime_1.jsx)("div", {
|
|
116
|
+
className: styles_module_scss_2.default.virtualizedPositionBox,
|
|
117
|
+
style: {
|
|
118
|
+
transform: `translateY(${(_b = (_a = virtualItems[0]) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : 0}px)`
|
|
119
|
+
},
|
|
120
|
+
tabIndex: -1,
|
|
121
|
+
children: virtualItems.map(virtualRow => (0, jsx_runtime_1.jsx)("div", {
|
|
122
|
+
"data-index": virtualRow.index,
|
|
123
|
+
ref: virtualizer.measureElement,
|
|
124
|
+
tabIndex: -1,
|
|
125
|
+
children: itemsJSX[virtualRow.index]
|
|
126
|
+
}, virtualRow.key))
|
|
127
|
+
})
|
|
128
|
+
}) : itemsJSX, loadingJSX, (0, jsx_runtime_1.jsx)(helperComponents_1.ListEmptyState, {
|
|
129
|
+
loading: loading,
|
|
130
|
+
dataError: dataError,
|
|
131
|
+
emptyStates: emptyStates,
|
|
132
|
+
hasNoItems: hasNoItems,
|
|
133
|
+
dataFiltered: dataFiltered !== null && dataFiltered !== void 0 ? dataFiltered : Boolean(search === null || search === void 0 ? void 0 : search.value)
|
|
134
|
+
})]
|
|
135
|
+
});
|
|
136
|
+
}, [dataError, dataFiltered, emptyStates, hasNoItems, itemsJSX, loading, loadingJSX, search === null || search === void 0 ? void 0 : search.value, virtualItems, virtualized, virtualizer]);
|
|
101
137
|
const onScrollInitialized = () => {
|
|
102
138
|
var _a, _b;
|
|
103
139
|
if (scrollToSelectedItem) {
|
|
@@ -136,7 +172,10 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
|
|
|
136
172
|
}, scrollContainerClassName),
|
|
137
173
|
barHideStrategy: 'never',
|
|
138
174
|
size: size === 's' ? 's' : 'm',
|
|
139
|
-
ref:
|
|
175
|
+
ref: ref => {
|
|
176
|
+
innerScrollRef.current = ref;
|
|
177
|
+
if (scrollContainerRef) scrollContainerRef.current = ref;
|
|
178
|
+
},
|
|
140
179
|
untouchableScrollbars: untouchableScrollbars,
|
|
141
180
|
onScroll: onScroll,
|
|
142
181
|
onInitialized: onScrollInitialized,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ALL_SIZES = exports.SMALL_SIZE_HEIGHT = exports.MEDIUM_SIZE_HEIGHT = exports.LARGE_SIZE_HEIGHT = void 0;
|
|
7
|
+
exports.LARGE_SIZE_HEIGHT = 48;
|
|
8
|
+
exports.MEDIUM_SIZE_HEIGHT = 40;
|
|
9
|
+
exports.SMALL_SIZE_HEIGHT = 32;
|
|
10
|
+
exports.ALL_SIZES = {
|
|
11
|
+
m: exports.MEDIUM_SIZE_HEIGHT,
|
|
12
|
+
s: exports.SMALL_SIZE_HEIGHT,
|
|
13
|
+
l: exports.LARGE_SIZE_HEIGHT
|
|
14
|
+
};
|
|
@@ -65,6 +65,10 @@ export type DroplistProps = {
|
|
|
65
65
|
* @default false
|
|
66
66
|
*/
|
|
67
67
|
closeDroplistOnItemClick?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Включить виртуализацию на компоненты списка. Рекомендуется если у вас от 1к элементов списка
|
|
70
|
+
*/
|
|
71
|
+
virtualized?: boolean;
|
|
68
72
|
/** Триггер для дроплиста
|
|
69
73
|
* @type ReactNode | ({onKeyDown}) => ReactNode
|
|
70
74
|
*
|
|
@@ -78,6 +82,7 @@ export type ListPrivateProps = Omit<ListProps, 'pinTop' | 'pinBottom' | 'items'
|
|
|
78
82
|
nested?: boolean;
|
|
79
83
|
active?: boolean;
|
|
80
84
|
tabIndex?: number;
|
|
85
|
+
virtualized?: boolean;
|
|
81
86
|
onFocus?(e: FocusEvent<HTMLElement>): void;
|
|
82
87
|
onBlur?(e: FocusEvent<HTMLElement>): void;
|
|
83
88
|
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { DroplistProps } from '../types';
|
|
2
|
-
export declare function Droplist({ items: itemsProp, search, pinBottom: pinBottomProp, pinTop: pinTopProp, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse, triggerClassName, selection, contentRender, size, marker, closeDroplistOnItemClick, className, listRef: listRefProp, untouchableScrollbars, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function Droplist({ items: itemsProp, search, pinBottom: pinBottomProp, pinTop: pinTopProp, footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse, triggerClassName, selection, contentRender, size, marker, closeDroplistOnItemClick, className, listRef: listRefProp, untouchableScrollbars, virtualized, ...props }: DroplistProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -23,7 +23,7 @@ import { ListPrivate } from '../ListPrivate';
|
|
|
23
23
|
import styles from '../styles.module.css';
|
|
24
24
|
const DEFAULT_FALLBACK_PLACEMENTS = ['top', 'right', 'bottom', 'left'];
|
|
25
25
|
export function Droplist(_a) {
|
|
26
|
-
var { items: itemsProp, search, pinBottom: pinBottomProp = [], pinTop: pinTopProp = [], footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse = {}, triggerClassName, selection, contentRender, size = 's', marker = true, closeDroplistOnItemClick = false, className, listRef: listRefProp, untouchableScrollbars = false } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange", "collapse", "triggerClassName", "selection", "contentRender", "size", "marker", "closeDroplistOnItemClick", "className", "listRef", "untouchableScrollbars"]);
|
|
26
|
+
var { items: itemsProp, search, pinBottom: pinBottomProp = [], pinTop: pinTopProp = [], footerActiveElementsRefs, children, trigger, placement, widthStrategy, triggerElemRef: triggerElemRefProp, open: openProp, onOpenChange, collapse = {}, triggerClassName, selection, contentRender, size = 's', marker = true, closeDroplistOnItemClick = false, className, listRef: listRefProp, untouchableScrollbars = false, virtualized = false } = _a, props = __rest(_a, ["items", "search", "pinBottom", "pinTop", "footerActiveElementsRefs", "children", "trigger", "placement", "widthStrategy", "triggerElemRef", "open", "onOpenChange", "collapse", "triggerClassName", "selection", "contentRender", "size", "marker", "closeDroplistOnItemClick", "className", "listRef", "untouchableScrollbars", "virtualized"]);
|
|
27
27
|
const hasSearch = useMemo(() => Boolean(search), [search]);
|
|
28
28
|
const [openCollapseItems = [], setOpenCollapsedItems] = useValueControl(collapse);
|
|
29
29
|
const toggleOpenCollapseItem = useCallback((id) => setOpenCollapsedItems((items) => (items === null || items === void 0 ? void 0 : items.includes(id)) ? items.filter(item => item !== id) : (items !== null && items !== void 0 ? items : []).concat([id])), [setOpenCollapsedItems]);
|
|
@@ -143,7 +143,7 @@ export function Droplist(_a) {
|
|
|
143
143
|
resetActiveItemId();
|
|
144
144
|
(_a = (triggerElemRefProp !== null && triggerElemRefProp !== void 0 ? triggerElemRefProp : triggerElemRef).current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
145
145
|
},
|
|
146
|
-
}, children: _jsx(Dropdown, { content: _jsx("div", { className: cn(styles.wrapper, className), children: _jsx(ListPrivate, Object.assign({}, props, { items: memorizedItems.items.focusCloseChildIds, pinTop: memorizedItems.pinTop.focusCloseChildIds, pinBottom: memorizedItems.pinBottom.focusCloseChildIds, onKeyDown: handleListKeyDown, searchItem: searchItem, tabIndex: 0, ref: mergeRefs(listRef, listRefProp), search: search, onFocus: e => {
|
|
146
|
+
}, children: _jsx(Dropdown, { content: _jsx("div", { className: cn(styles.wrapper, className), children: _jsx(ListPrivate, Object.assign({}, props, { items: memorizedItems.items.focusCloseChildIds, pinTop: memorizedItems.pinTop.focusCloseChildIds, pinBottom: memorizedItems.pinBottom.focusCloseChildIds, virtualized: virtualized, onKeyDown: handleListKeyDown, searchItem: searchItem, tabIndex: 0, ref: mergeRefs(listRef, listRefProp), search: search, onFocus: e => {
|
|
147
147
|
e.stopPropagation();
|
|
148
148
|
forceUpdateActiveItemId === null || forceUpdateActiveItemId === void 0 ? void 0 : forceUpdateActiveItemId(ids[0]);
|
|
149
149
|
}, limitedScrollHeight: true, untouchableScrollbars: untouchableScrollbars })) }), outsideClick: true, triggerClassName: triggerClassName, fallbackPlacements: DEFAULT_FALLBACK_PLACEMENTS, trigger: trigger, placement: placement, widthStrategy: widthStrategy, triggerRef: !triggerElemRefProp ? triggerElemRef : (isValid && triggerElemRefProp) || undefined, open: open, onOpenChange: handleOpenChange, children: triggerElem }) }) }) }) })) }));
|
|
@@ -2,6 +2,7 @@ export declare const ListPrivate: import("react").ForwardRefExoticComponent<Omit
|
|
|
2
2
|
nested?: boolean;
|
|
3
3
|
active?: boolean;
|
|
4
4
|
tabIndex?: number;
|
|
5
|
+
virtualized?: boolean;
|
|
5
6
|
onFocus?(e: import("react").FocusEvent<HTMLElement>): void;
|
|
6
7
|
onBlur?(e: import("react").FocusEvent<HTMLElement>): void;
|
|
7
8
|
onKeyDown?(e: import("react").KeyboardEvent<HTMLElement>): void;
|
|
@@ -10,8 +10,9 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
13
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
13
14
|
import cn from 'classnames';
|
|
14
|
-
import { forwardRef, useMemo } from 'react';
|
|
15
|
+
import { forwardRef, useEffect, useMemo, useRef } from 'react';
|
|
15
16
|
import { Spinner } from '@snack-uikit/loaders';
|
|
16
17
|
import { Scroll } from '@snack-uikit/scroll';
|
|
17
18
|
import { extractSupportProps } from '@snack-uikit/utils';
|
|
@@ -19,18 +20,45 @@ import { ListEmptyState, useEmptyState } from '../../../helperComponents';
|
|
|
19
20
|
import { PinBottomGroupItem, PinTopGroupItem, SearchItem, useRenderItems } from '../../Items';
|
|
20
21
|
import { useNewListContext, useSelectionContext } from '../contexts';
|
|
21
22
|
import commonStyles from '../styles.module.css';
|
|
23
|
+
import { ALL_SIZES } from './constants';
|
|
22
24
|
import styles from './styles.module.css';
|
|
23
25
|
export const ListPrivate = forwardRef((_a, ref) => {
|
|
24
|
-
var { items, pinTop, pinBottom, onKeyDown, onBlur, onFocus, tabIndex, active, scroll, nested, search, searchItem, scrollRef, scrollContainerRef, onScroll, footer, loading, limitedScrollHeight, untouchableScrollbars, className, noDataState, noResultsState, errorDataState, dataError, dataFiltered, scrollToSelectedItem = false, scrollContainerClassName } = _a, props = __rest(_a, ["items", "pinTop", "pinBottom", "onKeyDown", "onBlur", "onFocus", "tabIndex", "active", "scroll", "nested", "search", "searchItem", "scrollRef", "scrollContainerRef", "onScroll", "footer", "loading", "limitedScrollHeight", "untouchableScrollbars", "className", "noDataState", "noResultsState", "errorDataState", "dataError", "dataFiltered", "scrollToSelectedItem", "scrollContainerClassName"]);
|
|
26
|
+
var { items, pinTop, pinBottom, onKeyDown, onBlur, onFocus, tabIndex, active, scroll, nested, search, searchItem, scrollRef, scrollContainerRef, onScroll, footer, loading, limitedScrollHeight, untouchableScrollbars, className, noDataState, noResultsState, errorDataState, dataError, dataFiltered, scrollToSelectedItem = false, virtualized = false, scrollContainerClassName } = _a, props = __rest(_a, ["items", "pinTop", "pinBottom", "onKeyDown", "onBlur", "onFocus", "tabIndex", "active", "scroll", "nested", "search", "searchItem", "scrollRef", "scrollContainerRef", "onScroll", "footer", "loading", "limitedScrollHeight", "untouchableScrollbars", "className", "noDataState", "noResultsState", "errorDataState", "dataError", "dataFiltered", "scrollToSelectedItem", "virtualized", "scrollContainerClassName"]);
|
|
25
27
|
const { size = 's', flattenItems, focusFlattenItems } = useNewListContext();
|
|
26
28
|
const { value, isSelectionSingle } = useSelectionContext();
|
|
29
|
+
const innerScrollRef = useRef(null);
|
|
27
30
|
const itemsJSX = useRenderItems(items);
|
|
28
31
|
const itemsPinTopJSX = useRenderItems(pinTop);
|
|
29
32
|
const itemsPinBottomJSX = useRenderItems(pinBottom);
|
|
30
33
|
const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
|
|
31
34
|
const hasNoItems = items.length === 0;
|
|
35
|
+
const virtualizer = useVirtualizer({
|
|
36
|
+
count: itemsJSX.length,
|
|
37
|
+
getScrollElement: () => (scroll ? innerScrollRef.current : null),
|
|
38
|
+
estimateSize: () => ALL_SIZES[size],
|
|
39
|
+
enabled: virtualized,
|
|
40
|
+
});
|
|
41
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
virtualizer.measure();
|
|
44
|
+
}, [virtualizer]);
|
|
32
45
|
const loadingJSX = useMemo(() => loading && (_jsx("div", { role: 'spinbutton', tabIndex: -1, className: styles.loader, "data-size": size, "data-no-items": hasNoItems || undefined, "data-test-id": 'list__loader', children: _jsx(Spinner, { size: size === 'l' ? 's' : 'xs' }) })), [hasNoItems, loading, size]);
|
|
33
|
-
const content = useMemo(() =>
|
|
46
|
+
const content = useMemo(() => {
|
|
47
|
+
var _a, _b;
|
|
48
|
+
return (_jsxs("div", { className: styles.content, children: [virtualized ? (_jsx("div", { className: styles.virtualizedContainer, style: { height: virtualizer.getTotalSize() }, tabIndex: -1, children: _jsx("div", { className: styles.virtualizedPositionBox, style: { transform: `translateY(${(_b = (_a = virtualItems[0]) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : 0}px)` }, tabIndex: -1, children: virtualItems.map(virtualRow => (_jsx("div", { "data-index": virtualRow.index, ref: virtualizer.measureElement, tabIndex: -1, children: itemsJSX[virtualRow.index] }, virtualRow.key))) }) })) : (itemsJSX), loadingJSX, _jsx(ListEmptyState, { loading: loading, dataError: dataError, emptyStates: emptyStates, hasNoItems: hasNoItems, dataFiltered: dataFiltered !== null && dataFiltered !== void 0 ? dataFiltered : Boolean(search === null || search === void 0 ? void 0 : search.value) })] }));
|
|
49
|
+
}, [
|
|
50
|
+
dataError,
|
|
51
|
+
dataFiltered,
|
|
52
|
+
emptyStates,
|
|
53
|
+
hasNoItems,
|
|
54
|
+
itemsJSX,
|
|
55
|
+
loading,
|
|
56
|
+
loadingJSX,
|
|
57
|
+
search === null || search === void 0 ? void 0 : search.value,
|
|
58
|
+
virtualItems,
|
|
59
|
+
virtualized,
|
|
60
|
+
virtualizer,
|
|
61
|
+
]);
|
|
34
62
|
const onScrollInitialized = () => {
|
|
35
63
|
var _a, _b;
|
|
36
64
|
if (scrollToSelectedItem) {
|
|
@@ -49,7 +77,11 @@ export const ListPrivate = forwardRef((_a, ref) => {
|
|
|
49
77
|
[commonStyles.scrollContainerS]: scroll && limitedScrollHeight && size === 's',
|
|
50
78
|
[commonStyles.scrollContainerM]: scroll && limitedScrollHeight && size === 'm',
|
|
51
79
|
[commonStyles.scrollContainerL]: scroll && limitedScrollHeight && size === 'l',
|
|
52
|
-
}, scrollContainerClassName), barHideStrategy: 'never', size: size === 's' ? 's' : 'm', ref:
|
|
80
|
+
}, scrollContainerClassName), barHideStrategy: 'never', size: size === 's' ? 's' : 'm', ref: ref => {
|
|
81
|
+
innerScrollRef.current = ref;
|
|
82
|
+
if (scrollContainerRef)
|
|
83
|
+
scrollContainerRef.current = ref;
|
|
84
|
+
}, untouchableScrollbars: untouchableScrollbars, onScroll: onScroll, onInitialized: onScrollInitialized, children: [content, _jsx("div", { className: styles.scrollStub, ref: scrollRef })] })) : (_jsx(_Fragment, { children: content })), Number(pinBottom === null || pinBottom === void 0 ? void 0 : pinBottom.length) > 0 && _jsx(PinBottomGroupItem, { children: itemsPinBottomJSX }), footer && (_jsx("div", { className: styles.footer, onFocus: e => e.stopPropagation(), children: footer }))] })));
|
|
53
85
|
if (!nested) {
|
|
54
86
|
return listJSX;
|
|
55
87
|
}
|
|
@@ -65,6 +65,10 @@ export type DroplistProps = {
|
|
|
65
65
|
* @default false
|
|
66
66
|
*/
|
|
67
67
|
closeDroplistOnItemClick?: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Включить виртуализацию на компоненты списка. Рекомендуется если у вас от 1к элементов списка
|
|
70
|
+
*/
|
|
71
|
+
virtualized?: boolean;
|
|
68
72
|
/** Триггер для дроплиста
|
|
69
73
|
* @type ReactNode | ({onKeyDown}) => ReactNode
|
|
70
74
|
*
|
|
@@ -78,6 +82,7 @@ export type ListPrivateProps = Omit<ListProps, 'pinTop' | 'pinBottom' | 'items'
|
|
|
78
82
|
nested?: boolean;
|
|
79
83
|
active?: boolean;
|
|
80
84
|
tabIndex?: number;
|
|
85
|
+
virtualized?: boolean;
|
|
81
86
|
onFocus?(e: FocusEvent<HTMLElement>): void;
|
|
82
87
|
onBlur?(e: FocusEvent<HTMLElement>): void;
|
|
83
88
|
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
6
|
"title": "List",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.23.0",
|
|
8
8
|
"sideEffects": [
|
|
9
9
|
"*.css",
|
|
10
10
|
"*.woff",
|
|
@@ -40,18 +40,19 @@
|
|
|
40
40
|
"@snack-uikit/divider": "3.2.2",
|
|
41
41
|
"@snack-uikit/dropdown": "0.4.2",
|
|
42
42
|
"@snack-uikit/icons": "0.24.1",
|
|
43
|
-
"@snack-uikit/info-block": "0.6.8
|
|
43
|
+
"@snack-uikit/info-block": "0.6.8",
|
|
44
44
|
"@snack-uikit/loaders": "0.9.0",
|
|
45
45
|
"@snack-uikit/scroll": "0.9.2",
|
|
46
46
|
"@snack-uikit/search-private": "0.4.8",
|
|
47
|
-
"@snack-uikit/toggles": "0.13.4
|
|
48
|
-
"@snack-uikit/truncate-string": "0.6.5
|
|
47
|
+
"@snack-uikit/toggles": "0.13.4",
|
|
48
|
+
"@snack-uikit/truncate-string": "0.6.5",
|
|
49
49
|
"@snack-uikit/utils": "3.6.0",
|
|
50
|
+
"@tanstack/react-virtual": "3.11.2",
|
|
50
51
|
"classnames": "2.5.1",
|
|
51
52
|
"merge-refs": "1.3.0"
|
|
52
53
|
},
|
|
53
54
|
"peerDependencies": {
|
|
54
55
|
"@snack-uikit/locale": "*"
|
|
55
56
|
},
|
|
56
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "546ae486452e74e78f65fd9ce0044c13b058872c"
|
|
57
58
|
}
|
|
@@ -44,6 +44,7 @@ export function Droplist({
|
|
|
44
44
|
className,
|
|
45
45
|
listRef: listRefProp,
|
|
46
46
|
untouchableScrollbars = false,
|
|
47
|
+
virtualized = false,
|
|
47
48
|
...props
|
|
48
49
|
}: DroplistProps) {
|
|
49
50
|
const hasSearch = useMemo(() => Boolean(search), [search]);
|
|
@@ -240,6 +241,7 @@ export function Droplist({
|
|
|
240
241
|
items={memorizedItems.items.focusCloseChildIds}
|
|
241
242
|
pinTop={memorizedItems.pinTop.focusCloseChildIds}
|
|
242
243
|
pinBottom={memorizedItems.pinBottom.focusCloseChildIds}
|
|
244
|
+
virtualized={virtualized}
|
|
243
245
|
onKeyDown={handleListKeyDown}
|
|
244
246
|
searchItem={searchItem}
|
|
245
247
|
tabIndex={0}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
1
2
|
import cn from 'classnames';
|
|
2
|
-
import { ForwardedRef, forwardRef, RefObject, useMemo } from 'react';
|
|
3
|
+
import { ForwardedRef, forwardRef, RefObject, useEffect, useMemo, useRef } from 'react';
|
|
3
4
|
|
|
4
5
|
import { Spinner } from '@snack-uikit/loaders';
|
|
5
6
|
import { Scroll } from '@snack-uikit/scroll';
|
|
@@ -10,6 +11,7 @@ import { PinBottomGroupItem, PinTopGroupItem, SearchItem, useRenderItems } from
|
|
|
10
11
|
import { useNewListContext, useSelectionContext } from '../contexts';
|
|
11
12
|
import commonStyles from '../styles.module.scss';
|
|
12
13
|
import { ListPrivateProps } from '../types';
|
|
14
|
+
import { ALL_SIZES } from './constants';
|
|
13
15
|
import styles from './styles.module.scss';
|
|
14
16
|
|
|
15
17
|
export const ListPrivate = forwardRef(
|
|
@@ -41,6 +43,7 @@ export const ListPrivate = forwardRef(
|
|
|
41
43
|
dataError,
|
|
42
44
|
dataFiltered,
|
|
43
45
|
scrollToSelectedItem = false,
|
|
46
|
+
virtualized = false,
|
|
44
47
|
scrollContainerClassName,
|
|
45
48
|
...props
|
|
46
49
|
}: ListPrivateProps,
|
|
@@ -48,6 +51,7 @@ export const ListPrivate = forwardRef(
|
|
|
48
51
|
) => {
|
|
49
52
|
const { size = 's', flattenItems, focusFlattenItems } = useNewListContext();
|
|
50
53
|
const { value, isSelectionSingle } = useSelectionContext();
|
|
54
|
+
const innerScrollRef = useRef<HTMLElement | null>(null);
|
|
51
55
|
|
|
52
56
|
const itemsJSX = useRenderItems(items);
|
|
53
57
|
const itemsPinTopJSX = useRenderItems(pinTop);
|
|
@@ -56,6 +60,18 @@ export const ListPrivate = forwardRef(
|
|
|
56
60
|
const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
|
|
57
61
|
const hasNoItems = items.length === 0;
|
|
58
62
|
|
|
63
|
+
const virtualizer = useVirtualizer({
|
|
64
|
+
count: itemsJSX.length,
|
|
65
|
+
getScrollElement: () => (scroll ? innerScrollRef.current : null),
|
|
66
|
+
estimateSize: () => ALL_SIZES[size],
|
|
67
|
+
enabled: virtualized,
|
|
68
|
+
});
|
|
69
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
virtualizer.measure();
|
|
73
|
+
}, [virtualizer]);
|
|
74
|
+
|
|
59
75
|
const loadingJSX = useMemo(
|
|
60
76
|
() =>
|
|
61
77
|
loading && (
|
|
@@ -76,7 +92,28 @@ export const ListPrivate = forwardRef(
|
|
|
76
92
|
const content = useMemo(
|
|
77
93
|
() => (
|
|
78
94
|
<div className={styles.content}>
|
|
79
|
-
{
|
|
95
|
+
{virtualized ? (
|
|
96
|
+
<div className={styles.virtualizedContainer} style={{ height: virtualizer.getTotalSize() }} tabIndex={-1}>
|
|
97
|
+
<div
|
|
98
|
+
className={styles.virtualizedPositionBox}
|
|
99
|
+
style={{ transform: `translateY(${virtualItems[0]?.start ?? 0}px)` }}
|
|
100
|
+
tabIndex={-1}
|
|
101
|
+
>
|
|
102
|
+
{virtualItems.map(virtualRow => (
|
|
103
|
+
<div
|
|
104
|
+
key={virtualRow.key}
|
|
105
|
+
data-index={virtualRow.index}
|
|
106
|
+
ref={virtualizer.measureElement}
|
|
107
|
+
tabIndex={-1}
|
|
108
|
+
>
|
|
109
|
+
{itemsJSX[virtualRow.index]}
|
|
110
|
+
</div>
|
|
111
|
+
))}
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
) : (
|
|
115
|
+
itemsJSX
|
|
116
|
+
)}
|
|
80
117
|
{loadingJSX}
|
|
81
118
|
|
|
82
119
|
<ListEmptyState
|
|
@@ -88,7 +125,19 @@ export const ListPrivate = forwardRef(
|
|
|
88
125
|
/>
|
|
89
126
|
</div>
|
|
90
127
|
),
|
|
91
|
-
[
|
|
128
|
+
[
|
|
129
|
+
dataError,
|
|
130
|
+
dataFiltered,
|
|
131
|
+
emptyStates,
|
|
132
|
+
hasNoItems,
|
|
133
|
+
itemsJSX,
|
|
134
|
+
loading,
|
|
135
|
+
loadingJSX,
|
|
136
|
+
search?.value,
|
|
137
|
+
virtualItems,
|
|
138
|
+
virtualized,
|
|
139
|
+
virtualizer,
|
|
140
|
+
],
|
|
92
141
|
);
|
|
93
142
|
|
|
94
143
|
const onScrollInitialized = () => {
|
|
@@ -140,7 +189,10 @@ export const ListPrivate = forwardRef(
|
|
|
140
189
|
)}
|
|
141
190
|
barHideStrategy='never'
|
|
142
191
|
size={size === 's' ? 's' : 'm'}
|
|
143
|
-
ref={
|
|
192
|
+
ref={ref => {
|
|
193
|
+
innerScrollRef.current = ref;
|
|
194
|
+
if (scrollContainerRef) (scrollContainerRef as React.MutableRefObject<HTMLElement | null>).current = ref;
|
|
195
|
+
}}
|
|
144
196
|
untouchableScrollbars={untouchableScrollbars}
|
|
145
197
|
onScroll={onScroll}
|
|
146
198
|
onInitialized={onScrollInitialized}
|
|
@@ -77,6 +77,11 @@ export type DroplistProps = {
|
|
|
77
77
|
*/
|
|
78
78
|
closeDroplistOnItemClick?: boolean;
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Включить виртуализацию на компоненты списка. Рекомендуется если у вас от 1к элементов списка
|
|
82
|
+
*/
|
|
83
|
+
virtualized?: boolean;
|
|
84
|
+
|
|
80
85
|
/** Триггер для дроплиста
|
|
81
86
|
* @type ReactNode | ({onKeyDown}) => ReactNode
|
|
82
87
|
*
|
|
@@ -90,6 +95,7 @@ export type ListPrivateProps = Omit<ListProps, 'pinTop' | 'pinBottom' | 'items'
|
|
|
90
95
|
nested?: boolean;
|
|
91
96
|
active?: boolean;
|
|
92
97
|
tabIndex?: number;
|
|
98
|
+
virtualized?: boolean;
|
|
93
99
|
onFocus?(e: FocusEvent<HTMLElement>): void;
|
|
94
100
|
onBlur?(e: FocusEvent<HTMLElement>): void;
|
|
95
101
|
onKeyDown?(e: KeyboardEvent<HTMLElement>): void;
|