@snack-uikit/list 0.29.7-preview-54496a2c.0 → 0.29.7-preview-c824da44.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.
@@ -33,6 +33,7 @@ const styles_module_scss_1 = __importDefault(require('../styles.module.css'));
33
33
  const constants_1 = require("./constants");
34
34
  const styles_module_scss_2 = __importDefault(require('./styles.module.css'));
35
35
  exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
36
+ var _b;
36
37
  var {
37
38
  items,
38
39
  pinTop,
@@ -78,7 +79,12 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
78
79
  const itemsJSX = (0, Items_1.useRenderItems)(items);
79
80
  const itemsPinTopJSX = (0, Items_1.useRenderItems)(pinTop);
80
81
  const itemsPinBottomJSX = (0, Items_1.useRenderItems)(pinBottom);
81
- const lastScrolledItemIndex = (0, react_1.useRef)(null);
82
+ const [scrollState, setScrollState] = (0, react_1.useState)({
83
+ virtualizer: null,
84
+ browser: null,
85
+ measured: false
86
+ });
87
+ console.info('[DEBUG] ScrollState', scrollState);
82
88
  const emptyStates = (0, helperComponents_1.useEmptyState)({
83
89
  noDataState,
84
90
  noResultsState,
@@ -119,38 +125,64 @@ exports.ListPrivate = (0, react_1.forwardRef)((_a, ref) => {
119
125
  });
120
126
  const virtualItems = virtualizer.getVirtualItems();
121
127
  (0, react_1.useEffect)(() => {
122
- virtualizer.measure();
123
- }, [virtualizer]);
128
+ if (scrollState.measured) {
129
+ return;
130
+ }
131
+ virtualizer.measure(); // TODO: перезамерять размер
132
+ setScrollState(prevState => Object.assign(Object.assign({}, prevState), {
133
+ measured: true
134
+ }));
135
+ }, [scrollState.measured, virtualizer]);
124
136
  const isScrollToItemEnabled = scroll && scrollToSelectedItem && virtualized;
125
137
  (0, react_1.useEffect)(() => {
126
138
  var _a;
127
139
  if (isScrollToItemEnabled) {
140
+ console.info('[DEBUG] selectedItemIndex', selectedItemIndex);
141
+ if (!scrollState.measured) {
142
+ console.info('[DEBUG] Not measured yet');
143
+ return; // Not measured yet
144
+ }
128
145
  if (selectedItemIndex < 0 || !selectedItem) {
146
+ console.info('[DEBUG] Cannot scroll to non-existing item');
129
147
  return; // Cannot scroll to non-existing item
130
148
  }
131
- if (lastScrolledItemIndex.current === selectedItemIndex) {
149
+ if (scrollState.virtualizer === selectedItemIndex) {
150
+ console.info('[DEBUG] No need to re-scroll to the same item during re-renders');
132
151
  return; // No need to re-scroll to the same item during re-renders
133
152
  }
134
153
  if ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) && ((_a = innerScrollRef.current) === null || _a === void 0 ? void 0 : _a.contains(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef.current))) {
154
+ console.info('[DEBUG] No need to scroll to manually clicked item currently present in DOM');
135
155
  return; // No need to scroll to manually clicked item currently present in DOM
136
156
  }
137
- setTimeout(() => {
138
- // Scroll to item by virtualizer method => required item will appear in DOM, but not perfectly centered
139
- virtualizer.scrollToIndex(selectedItemIndex, {
140
- align: 'center'
141
- });
142
- setTimeout(() => {
143
- var _a, _b;
144
- // Scroll to item already present in DOM by browser method – fixes imperfect virtualizer centering.
145
- // We need perfect centering to avoid desired item from being overlapped by pinned groups (top or bottom)
146
- (_b = (_a = selectedItem.itemRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.scrollIntoView({
147
- block: 'center'
148
- });
149
- lastScrolledItemIndex.current = selectedItemIndex;
150
- }, 0);
151
- }, 0);
157
+ console.info('[DEBUG] SCROLLING BY VIRTUALIZER');
158
+ virtualizer.scrollToIndex(selectedItemIndex, {
159
+ align: 'center'
160
+ });
161
+ setScrollState(prevState => Object.assign(Object.assign({}, prevState), {
162
+ virtualizer: selectedItemIndex
163
+ }));
164
+ }
165
+ }, [isScrollToItemEnabled, scrollState, selectedItem, selectedItemIndex, virtualizer]);
166
+ const isTargetPresentInDom = Boolean((_b = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) === null || _b === void 0 ? void 0 : _b.current);
167
+ (0, react_1.useEffect)(() => {
168
+ var _a, _b, _c;
169
+ if (scrollState.virtualizer === null) {
170
+ return; // Not scrolled by virtualizer yet, no need for additional scroll
171
+ }
172
+ if (!isTargetPresentInDom) {
173
+ return; // Target element is not present in DOM yet, additional scroll does not work without it
174
+ }
175
+ if (scrollState.virtualizer === scrollState.browser) {
176
+ return; // Virtualizer scroll has not been executed => no need for additional scroll
152
177
  }
153
- }, [isScrollToItemEnabled, selectedItem, selectedItemIndex, virtualizer]);
178
+ console.info('[DEBUG] SCROLLING BY BROWSER', (_a = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) === null || _a === void 0 ? void 0 : _a.current);
179
+ (_c = (_b = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) === null || _b === void 0 ? void 0 : _b.current) === null || _c === void 0 ? void 0 : _c.scrollIntoView({
180
+ block: 'center'
181
+ });
182
+ setScrollState(prevState => Object.assign(Object.assign({}, prevState), {
183
+ browser: selectedItemIndex
184
+ }));
185
+ }, [scrollState, selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef, isTargetPresentInDom, selectedItemIndex]);
154
186
  const loadingJSX = (0, react_1.useMemo)(() => loading && (0, jsx_runtime_1.jsx)("div", {
155
187
  role: 'spinbutton',
156
188
  tabIndex: -1,
@@ -13,7 +13,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
13
13
  import { useVirtualizer } from '@tanstack/react-virtual';
14
14
  import cn from 'classnames';
15
15
  import mergeRefs from 'merge-refs';
16
- import { forwardRef, useCallback, useEffect, useMemo, useRef } from 'react';
16
+ import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react';
17
17
  import { Spinner } from '@snack-uikit/loaders';
18
18
  import { Scroll } from '@snack-uikit/scroll';
19
19
  import { extractSupportProps } from '@snack-uikit/utils';
@@ -25,6 +25,7 @@ import commonStyles from '../styles.module.css';
25
25
  import { ALL_SIZES } from './constants';
26
26
  import styles from './styles.module.css';
27
27
  export const ListPrivate = forwardRef((_a, ref) => {
28
+ var _b;
28
29
  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, barHideStrategy = 'never' } = _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", "barHideStrategy"]);
29
30
  const { size = 's', flattenItems, focusFlattenItems } = useNewListContext();
30
31
  const { value, isSelectionSingle } = useSelectionContext();
@@ -32,7 +33,8 @@ export const ListPrivate = forwardRef((_a, ref) => {
32
33
  const itemsJSX = useRenderItems(items);
33
34
  const itemsPinTopJSX = useRenderItems(pinTop);
34
35
  const itemsPinBottomJSX = useRenderItems(pinBottom);
35
- const lastScrolledItemIndex = useRef(null);
36
+ const [scrollState, setScrollState] = useState({ virtualizer: null, browser: null, measured: false });
37
+ console.info('[DEBUG] ScrollState', scrollState);
36
38
  const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
37
39
  const hasNoItems = items.length === 0;
38
40
  const { selectedItemIndex, selectedItem } = useMemo(() => {
@@ -66,34 +68,54 @@ export const ListPrivate = forwardRef((_a, ref) => {
66
68
  });
67
69
  const virtualItems = virtualizer.getVirtualItems();
68
70
  useEffect(() => {
69
- virtualizer.measure();
70
- }, [virtualizer]);
71
+ if (scrollState.measured) {
72
+ return;
73
+ }
74
+ virtualizer.measure(); // TODO: перезамерять размер
75
+ setScrollState(prevState => (Object.assign(Object.assign({}, prevState), { measured: true })));
76
+ }, [scrollState.measured, virtualizer]);
71
77
  const isScrollToItemEnabled = scroll && scrollToSelectedItem && virtualized;
72
78
  useEffect(() => {
73
79
  var _a;
74
80
  if (isScrollToItemEnabled) {
81
+ console.info('[DEBUG] selectedItemIndex', selectedItemIndex);
82
+ if (!scrollState.measured) {
83
+ console.info('[DEBUG] Not measured yet');
84
+ return; // Not measured yet
85
+ }
75
86
  if (selectedItemIndex < 0 || !selectedItem) {
87
+ console.info('[DEBUG] Cannot scroll to non-existing item');
76
88
  return; // Cannot scroll to non-existing item
77
89
  }
78
- if (lastScrolledItemIndex.current === selectedItemIndex) {
90
+ if (scrollState.virtualizer === selectedItemIndex) {
91
+ console.info('[DEBUG] No need to re-scroll to the same item during re-renders');
79
92
  return; // No need to re-scroll to the same item during re-renders
80
93
  }
81
94
  if ((selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) && ((_a = innerScrollRef.current) === null || _a === void 0 ? void 0 : _a.contains(selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef.current))) {
95
+ console.info('[DEBUG] No need to scroll to manually clicked item currently present in DOM');
82
96
  return; // No need to scroll to manually clicked item currently present in DOM
83
97
  }
84
- setTimeout(() => {
85
- // Scroll to item by virtualizer method => required item will appear in DOM, but not perfectly centered
86
- virtualizer.scrollToIndex(selectedItemIndex, { align: 'center' });
87
- setTimeout(() => {
88
- var _a, _b;
89
- // Scroll to item already present in DOM by browser method fixes imperfect virtualizer centering.
90
- // We need perfect centering to avoid desired item from being overlapped by pinned groups (top or bottom)
91
- (_b = (_a = selectedItem.itemRef) === null || _a === void 0 ? void 0 : _a.current) === null || _b === void 0 ? void 0 : _b.scrollIntoView({ block: 'center' });
92
- lastScrolledItemIndex.current = selectedItemIndex;
93
- }, 0);
94
- }, 0);
98
+ console.info('[DEBUG] SCROLLING BY VIRTUALIZER');
99
+ virtualizer.scrollToIndex(selectedItemIndex, { align: 'center' });
100
+ setScrollState(prevState => (Object.assign(Object.assign({}, prevState), { virtualizer: selectedItemIndex })));
101
+ }
102
+ }, [isScrollToItemEnabled, scrollState, selectedItem, selectedItemIndex, virtualizer]);
103
+ const isTargetPresentInDom = Boolean((_b = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) === null || _b === void 0 ? void 0 : _b.current);
104
+ useEffect(() => {
105
+ var _a, _b, _c;
106
+ if (scrollState.virtualizer === null) {
107
+ return; // Not scrolled by virtualizer yet, no need for additional scroll
108
+ }
109
+ if (!isTargetPresentInDom) {
110
+ return; // Target element is not present in DOM yet, additional scroll does not work without it
111
+ }
112
+ if (scrollState.virtualizer === scrollState.browser) {
113
+ return; // Virtualizer scroll has not been executed => no need for additional scroll
95
114
  }
96
- }, [isScrollToItemEnabled, selectedItem, selectedItemIndex, virtualizer]);
115
+ console.info('[DEBUG] SCROLLING BY BROWSER', (_a = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) === null || _a === void 0 ? void 0 : _a.current);
116
+ (_c = (_b = selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef) === null || _b === void 0 ? void 0 : _b.current) === null || _c === void 0 ? void 0 : _c.scrollIntoView({ block: 'center' });
117
+ setScrollState(prevState => (Object.assign(Object.assign({}, prevState), { browser: selectedItemIndex })));
118
+ }, [scrollState, selectedItem === null || selectedItem === void 0 ? void 0 : selectedItem.itemRef, isTargetPresentInDom, selectedItemIndex]);
97
119
  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]);
98
120
  const content = useMemo(() => (_jsxs(_Fragment, { children: [virtualized ? (_jsx("div", { className: styles.virtualizedContainer, style: { height: virtualizer.getTotalSize() }, tabIndex: -1, children: virtualItems.map(virtualRow => (_jsx("div", { "data-index": virtualRow.index, ref: virtualizer.measureElement, tabIndex: -1, className: styles.virtualizedPositionBox, style: {
99
121
  transform: `translateY(${virtualRow.start}px)`,
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "access": "public"
5
5
  },
6
6
  "title": "List",
7
- "version": "0.29.7-preview-54496a2c.0",
7
+ "version": "0.29.7-preview-c824da44.0",
8
8
  "sideEffects": [
9
9
  "*.css",
10
10
  "*.woff",
@@ -54,5 +54,5 @@
54
54
  "peerDependencies": {
55
55
  "@snack-uikit/locale": "*"
56
56
  },
57
- "gitHead": "456b961bf9efadad58676e3568e48529b85c7bcf"
57
+ "gitHead": "edea7e461de9f4f112e73ed1de8c1063240d3164"
58
58
  }
@@ -1,7 +1,7 @@
1
1
  import { useVirtualizer } from '@tanstack/react-virtual';
2
2
  import cn from 'classnames';
3
3
  import mergeRefs from 'merge-refs';
4
- import { ForwardedRef, forwardRef, RefObject, useCallback, useEffect, useMemo, useRef } from 'react';
4
+ import { ForwardedRef, forwardRef, RefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
5
 
6
6
  import { Spinner } from '@snack-uikit/loaders';
7
7
  import { Scroll } from '@snack-uikit/scroll';
@@ -16,6 +16,12 @@ import { ListPrivateProps } from '../types';
16
16
  import { ALL_SIZES } from './constants';
17
17
  import styles from './styles.module.scss';
18
18
 
19
+ type ScrollState = {
20
+ virtualizer: number | null;
21
+ browser: number | null;
22
+ measured: boolean;
23
+ };
24
+
19
25
  export const ListPrivate = forwardRef(
20
26
  (
21
27
  {
@@ -60,7 +66,8 @@ export const ListPrivate = forwardRef(
60
66
  const itemsPinTopJSX = useRenderItems(pinTop);
61
67
  const itemsPinBottomJSX = useRenderItems(pinBottom);
62
68
 
63
- const lastScrolledItemIndex = useRef<number | null>(null);
69
+ const [scrollState, setScrollState] = useState<ScrollState>({ virtualizer: null, browser: null, measured: false });
70
+ console.info('[DEBUG] ScrollState', scrollState);
64
71
 
65
72
  const emptyStates = useEmptyState({ noDataState, noResultsState, errorDataState });
66
73
  const hasNoItems = items.length === 0;
@@ -102,36 +109,74 @@ export const ListPrivate = forwardRef(
102
109
  const virtualItems = virtualizer.getVirtualItems();
103
110
 
104
111
  useEffect(() => {
105
- virtualizer.measure();
106
- }, [virtualizer]);
112
+ if (scrollState.measured) {
113
+ return;
114
+ }
115
+
116
+ virtualizer.measure(); // TODO: перезамерять размер
117
+
118
+ setScrollState(prevState => ({
119
+ ...prevState,
120
+ measured: true,
121
+ }));
122
+ }, [scrollState.measured, virtualizer]);
107
123
 
108
124
  const isScrollToItemEnabled = scroll && scrollToSelectedItem && virtualized;
109
125
 
110
126
  useEffect(() => {
111
127
  if (isScrollToItemEnabled) {
128
+ console.info('[DEBUG] selectedItemIndex', selectedItemIndex);
129
+ if (!scrollState.measured) {
130
+ console.info('[DEBUG] Not measured yet');
131
+ return; // Not measured yet
132
+ }
112
133
  if (selectedItemIndex < 0 || !selectedItem) {
134
+ console.info('[DEBUG] Cannot scroll to non-existing item');
113
135
  return; // Cannot scroll to non-existing item
114
136
  }
115
- if (lastScrolledItemIndex.current === selectedItemIndex) {
137
+ if (scrollState.virtualizer === selectedItemIndex) {
138
+ console.info('[DEBUG] No need to re-scroll to the same item during re-renders');
116
139
  return; // No need to re-scroll to the same item during re-renders
117
140
  }
118
141
  if (selectedItem?.itemRef && innerScrollRef.current?.contains(selectedItem?.itemRef.current)) {
142
+ console.info('[DEBUG] No need to scroll to manually clicked item currently present in DOM');
119
143
  return; // No need to scroll to manually clicked item currently present in DOM
120
144
  }
121
145
 
122
- setTimeout(() => {
123
- // Scroll to item by virtualizer method => required item will appear in DOM, but not perfectly centered
124
- virtualizer.scrollToIndex(selectedItemIndex, { align: 'center' });
125
-
126
- setTimeout(() => {
127
- // Scroll to item already present in DOM by browser method – fixes imperfect virtualizer centering.
128
- // We need perfect centering to avoid desired item from being overlapped by pinned groups (top or bottom)
129
- selectedItem.itemRef?.current?.scrollIntoView({ block: 'center' });
130
- lastScrolledItemIndex.current = selectedItemIndex;
131
- }, 0);
132
- }, 0);
146
+ console.info('[DEBUG] SCROLLING BY VIRTUALIZER');
147
+ virtualizer.scrollToIndex(selectedItemIndex, { align: 'center' });
148
+
149
+ setScrollState(prevState => ({
150
+ ...prevState,
151
+ virtualizer: selectedItemIndex,
152
+ }));
133
153
  }
134
- }, [isScrollToItemEnabled, selectedItem, selectedItemIndex, virtualizer]);
154
+ }, [isScrollToItemEnabled, scrollState, selectedItem, selectedItemIndex, virtualizer]);
155
+
156
+ const isTargetPresentInDom = Boolean(selectedItem?.itemRef?.current);
157
+
158
+ useEffect(() => {
159
+ if (scrollState.virtualizer === null) {
160
+ return; // Not scrolled by virtualizer yet, no need for additional scroll
161
+ }
162
+
163
+ if (!isTargetPresentInDom) {
164
+ return; // Target element is not present in DOM yet, additional scroll does not work without it
165
+ }
166
+
167
+ if (scrollState.virtualizer === scrollState.browser) {
168
+ return; // Virtualizer scroll has not been executed => no need for additional scroll
169
+ }
170
+
171
+ console.info('[DEBUG] SCROLLING BY BROWSER', selectedItem?.itemRef?.current);
172
+
173
+ selectedItem?.itemRef?.current?.scrollIntoView({ block: 'center' });
174
+
175
+ setScrollState(prevState => ({
176
+ ...prevState,
177
+ browser: selectedItemIndex,
178
+ }));
179
+ }, [scrollState, selectedItem?.itemRef, isTargetPresentInDom, selectedItemIndex]);
135
180
 
136
181
  const loadingJSX = useMemo(
137
182
  () =>