@snack-uikit/list 0.22.2 → 0.22.3-preview-0ee176c6.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.
@@ -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");
@@ -68,6 +69,7 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
68
69
  value,
69
70
  isSelectionSingle
70
71
  } = (0, contexts_1.useSelectionContext)();
72
+ const innerScrollRef = (0, react_1.useRef)(null);
71
73
  const itemsJSX = (0, Items_1.useRenderItems)(items);
72
74
  const itemsPinTopJSX = (0, Items_1.useRenderItems)(pinTop);
73
75
  const itemsPinBottomJSX = (0, Items_1.useRenderItems)(pinBottom);
@@ -77,6 +79,12 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
77
79
  errorDataState
78
80
  });
79
81
  const hasNoItems = items.length === 0;
82
+ const virtualizer = (0, react_virtual_1.useVirtualizer)({
83
+ count: itemsJSX.length,
84
+ getScrollElement: () => scroll ? innerScrollRef.current : null,
85
+ estimateSize: () => size === 'm' ? 40 : 32
86
+ });
87
+ const virtualItems = virtualizer.getVirtualItems();
80
88
  const loadingJSX = (0, react_1.useMemo)(() => loading && (0, jsx_runtime_1.jsx)("div", {
81
89
  role: 'spinbutton',
82
90
  tabIndex: -1,
@@ -88,18 +96,42 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
88
96
  size: size === 'l' ? 's' : 'xs'
89
97
  })
90
98
  }), [hasNoItems, loading, size]);
91
- const content = (0, react_1.useMemo)(() => (0, jsx_runtime_1.jsxs)("div", {
92
- className: styles_module_scss_2.default.content,
93
- children: [itemsJSX, loadingJSX, (0, jsx_runtime_1.jsx)(helperComponents_1.ListEmptyState, {
94
- loading: loading,
95
- dataError: dataError,
96
- emptyStates: emptyStates,
97
- hasNoItems: hasNoItems,
98
- dataFiltered: dataFiltered !== null && dataFiltered !== void 0 ? dataFiltered : Boolean(search === null || search === void 0 ? void 0 : search.value)
99
- })]
100
- }), [dataError, dataFiltered, emptyStates, hasNoItems, itemsJSX, loading, loadingJSX, search === null || search === void 0 ? void 0 : search.value]);
99
+ const content = (0, react_1.useMemo)(() => {
100
+ var _a, _b;
101
+ return (0, jsx_runtime_1.jsxs)("div", {
102
+ className: styles_module_scss_2.default.content,
103
+ children: [(0, jsx_runtime_1.jsx)("div", {
104
+ style: {
105
+ height: virtualizer.getTotalSize(),
106
+ width: '100%',
107
+ position: 'relative'
108
+ },
109
+ children: (0, jsx_runtime_1.jsx)("div", {
110
+ style: {
111
+ position: 'absolute',
112
+ top: 0,
113
+ left: 0,
114
+ width: '100%',
115
+ transform: `translateY(${(_b = (_a = virtualItems[0]) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : 0}px)`
116
+ },
117
+ children: virtualItems.map(virtualRow => (0, jsx_runtime_1.jsx)("div", {
118
+ "data-index": virtualRow.index,
119
+ ref: virtualizer.measureElement,
120
+ children: itemsJSX[virtualRow.index]
121
+ }, virtualRow.key))
122
+ })
123
+ }), loadingJSX, (0, jsx_runtime_1.jsx)(helperComponents_1.ListEmptyState, {
124
+ loading: loading,
125
+ dataError: dataError,
126
+ emptyStates: emptyStates,
127
+ hasNoItems: hasNoItems,
128
+ dataFiltered: dataFiltered !== null && dataFiltered !== void 0 ? dataFiltered : Boolean(search === null || search === void 0 ? void 0 : search.value)
129
+ })]
130
+ });
131
+ }, [dataError, dataFiltered, emptyStates, hasNoItems, itemsJSX, loading, loadingJSX, search === null || search === void 0 ? void 0 : search.value, virtualItems, virtualizer]);
101
132
  const onScrollInitialized = () => {
102
133
  var _a, _b;
134
+ virtualizer.measure();
103
135
  if (scrollToSelectedItem) {
104
136
  if (!value) {
105
137
  return;
@@ -136,7 +168,10 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
136
168
  }, scrollContainerClassName),
137
169
  barHideStrategy: 'never',
138
170
  size: size === 's' ? 's' : 'm',
139
- ref: scrollContainerRef,
171
+ ref: ref => {
172
+ innerScrollRef.current = ref;
173
+ if (scrollContainerRef) scrollContainerRef.current = ref;
174
+ },
140
175
  untouchableScrollbars: untouchableScrollbars,
141
176
  onScroll: onScroll,
142
177
  onInitialized: onScrollInitialized,
@@ -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, 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';
@@ -24,15 +25,47 @@ export const ListPrivate = forwardRef((_a, ref) => {
24
25
  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"]);
25
26
  const { size = 's', flattenItems, focusFlattenItems } = useNewListContext();
26
27
  const { value, isSelectionSingle } = useSelectionContext();
28
+ const innerScrollRef = useRef(null);
27
29
  const itemsJSX = useRenderItems(items);
28
30
  const itemsPinTopJSX = useRenderItems(pinTop);
29
31
  const itemsPinBottomJSX = useRenderItems(pinBottom);
30
32
  const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
31
33
  const hasNoItems = items.length === 0;
34
+ const virtualizer = useVirtualizer({
35
+ count: itemsJSX.length,
36
+ getScrollElement: () => (scroll ? innerScrollRef.current : null),
37
+ estimateSize: () => (size === 'm' ? 40 : 32),
38
+ });
39
+ const virtualItems = virtualizer.getVirtualItems();
32
40
  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(() => (_jsxs("div", { className: styles.content, children: [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) })] })), [dataError, dataFiltered, emptyStates, hasNoItems, itemsJSX, loading, loadingJSX, search === null || search === void 0 ? void 0 : search.value]);
41
+ const content = useMemo(() => {
42
+ var _a, _b;
43
+ return (_jsxs("div", { className: styles.content, children: [_jsx("div", { style: {
44
+ height: virtualizer.getTotalSize(),
45
+ width: '100%',
46
+ position: 'relative',
47
+ }, children: _jsx("div", { style: {
48
+ position: 'absolute',
49
+ top: 0,
50
+ left: 0,
51
+ width: '100%',
52
+ transform: `translateY(${(_b = (_a = virtualItems[0]) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : 0}px)`,
53
+ }, children: virtualItems.map(virtualRow => (_jsx("div", { "data-index": virtualRow.index, ref: virtualizer.measureElement, children: itemsJSX[virtualRow.index] }, virtualRow.key))) }) }), 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) })] }));
54
+ }, [
55
+ dataError,
56
+ dataFiltered,
57
+ emptyStates,
58
+ hasNoItems,
59
+ itemsJSX,
60
+ loading,
61
+ loadingJSX,
62
+ search === null || search === void 0 ? void 0 : search.value,
63
+ virtualItems,
64
+ virtualizer,
65
+ ]);
34
66
  const onScrollInitialized = () => {
35
67
  var _a, _b;
68
+ virtualizer.measure();
36
69
  if (scrollToSelectedItem) {
37
70
  if (!value) {
38
71
  return;
@@ -49,7 +82,11 @@ export const ListPrivate = forwardRef((_a, ref) => {
49
82
  [commonStyles.scrollContainerS]: scroll && limitedScrollHeight && size === 's',
50
83
  [commonStyles.scrollContainerM]: scroll && limitedScrollHeight && size === 'm',
51
84
  [commonStyles.scrollContainerL]: scroll && limitedScrollHeight && size === 'l',
52
- }, scrollContainerClassName), barHideStrategy: 'never', size: size === 's' ? 's' : 'm', ref: scrollContainerRef, 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 }))] })));
85
+ }, scrollContainerClassName), barHideStrategy: 'never', size: size === 's' ? 's' : 'm', ref: ref => {
86
+ innerScrollRef.current = ref;
87
+ if (scrollContainerRef)
88
+ scrollContainerRef.current = ref;
89
+ }, 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
90
  if (!nested) {
54
91
  return listJSX;
55
92
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "List",
7
- "version": "0.22.2",
7
+ "version": "0.22.3-preview-0ee176c6.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -47,11 +47,12 @@
47
47
  "@snack-uikit/toggles": "0.13.3",
48
48
  "@snack-uikit/truncate-string": "0.6.4",
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": "33621b09b3ca6d177fd8f41e5516566dee9f416a"
57
+ "gitHead": "cea4c91459901820b30d140dba4383987fe86583"
57
58
  }
@@ -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, useMemo, useRef } from 'react';
3
4
 
4
5
  import { Spinner } from '@snack-uikit/loaders';
5
6
  import { Scroll } from '@snack-uikit/scroll';
@@ -48,6 +49,7 @@ export const ListPrivate = forwardRef(
48
49
  ) => {
49
50
  const { size = 's', flattenItems, focusFlattenItems } = useNewListContext();
50
51
  const { value, isSelectionSingle } = useSelectionContext();
52
+ const innerScrollRef = useRef<HTMLElement | null>(null);
51
53
 
52
54
  const itemsJSX = useRenderItems(items);
53
55
  const itemsPinTopJSX = useRenderItems(pinTop);
@@ -56,6 +58,13 @@ export const ListPrivate = forwardRef(
56
58
  const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
57
59
  const hasNoItems = items.length === 0;
58
60
 
61
+ const virtualizer = useVirtualizer({
62
+ count: itemsJSX.length,
63
+ getScrollElement: () => (scroll ? innerScrollRef.current : null),
64
+ estimateSize: () => (size === 'm' ? 40 : 32),
65
+ });
66
+ const virtualItems = virtualizer.getVirtualItems();
67
+
59
68
  const loadingJSX = useMemo(
60
69
  () =>
61
70
  loading && (
@@ -76,7 +85,29 @@ export const ListPrivate = forwardRef(
76
85
  const content = useMemo(
77
86
  () => (
78
87
  <div className={styles.content}>
79
- {itemsJSX}
88
+ <div
89
+ style={{
90
+ height: virtualizer.getTotalSize(),
91
+ width: '100%',
92
+ position: 'relative',
93
+ }}
94
+ >
95
+ <div
96
+ style={{
97
+ position: 'absolute',
98
+ top: 0,
99
+ left: 0,
100
+ width: '100%',
101
+ transform: `translateY(${virtualItems[0]?.start ?? 0}px)`,
102
+ }}
103
+ >
104
+ {virtualItems.map(virtualRow => (
105
+ <div key={virtualRow.key} data-index={virtualRow.index} ref={virtualizer.measureElement}>
106
+ {itemsJSX[virtualRow.index]}
107
+ </div>
108
+ ))}
109
+ </div>
110
+ </div>
80
111
  {loadingJSX}
81
112
 
82
113
  <ListEmptyState
@@ -88,10 +119,23 @@ export const ListPrivate = forwardRef(
88
119
  />
89
120
  </div>
90
121
  ),
91
- [dataError, dataFiltered, emptyStates, hasNoItems, itemsJSX, loading, loadingJSX, search?.value],
122
+ [
123
+ dataError,
124
+ dataFiltered,
125
+ emptyStates,
126
+ hasNoItems,
127
+ itemsJSX,
128
+ loading,
129
+ loadingJSX,
130
+ search?.value,
131
+ virtualItems,
132
+ virtualizer,
133
+ ],
92
134
  );
93
135
 
94
136
  const onScrollInitialized = () => {
137
+ virtualizer.measure();
138
+
95
139
  if (scrollToSelectedItem) {
96
140
  if (!value) {
97
141
  return;
@@ -140,7 +184,10 @@ export const ListPrivate = forwardRef(
140
184
  )}
141
185
  barHideStrategy='never'
142
186
  size={size === 's' ? 's' : 'm'}
143
- ref={scrollContainerRef}
187
+ ref={ref => {
188
+ innerScrollRef.current = ref;
189
+ if (scrollContainerRef) (scrollContainerRef as React.MutableRefObject<HTMLElement | null>).current = ref;
190
+ }}
144
191
  untouchableScrollbars={untouchableScrollbars}
145
192
  onScroll={onScroll}
146
193
  onInitialized={onScrollInitialized}