@legendapp/list 1.0.9 → 1.0.11

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 (4) hide show
  1. package/CHANGELOG.md +48 -2
  2. package/index.js +44 -41
  3. package/index.mjs +44 -41
  4. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,51 @@
1
- # 1.0.0
1
+ ## 1.0.10
2
+ - Fix: Removed an optimization that only checked newly visible items, which could sometimes cause gaps in lists
3
+ - Fix: Scroll history resets properly during scroll operations, which was causing gaps after scroll
4
+ - Fix: Made scroll buffer calculations and scroll jump handling more reliable
2
5
 
6
+ ## 1.0.9
7
+ - Fix: Use the `use-sync-external-store` shim to support older versions of react
8
+ - Fix: Lists sometimes leaving some gaps when reordering a list
9
+ - Fix: Sometimes precomputing next scroll position for calculation incorrectly
10
+
11
+ ## 1.0.8
12
+ - Perf: The scroll buffering algorithm is smarter and adjusts based on scroll direction for better performance
13
+ - Perf: The container-finding logic keeps index order, reducing gaps in rendering
14
+ - Perf: Combine multiple hooks in Container to a single `useArray$` hook
15
+
16
+ ## 1.0.7
17
+ - Fix: Containers that move out of view are handled better
18
+
19
+ ## 1.0.6
20
+ - Fix: Average item size calculations are more accurate while scrolling
21
+ - Fix: Items in view are handled better when data changes
22
+ - Fix: Scroll position is maintained more accurately during updates
23
+
24
+ ## 1.0.5
25
+ - Fix: Fast scrolling sometimes caused elements to disappear
26
+ - Fix: Out-of-range `scrollToIndex` calls are handled better
27
+
28
+ ## 1.0.4
29
+ - Fix: Container allocation is more efficient
30
+ - Fix: Bidirectional infinite lists scroll better on the old architecture
31
+ - Fix: Item size updates are handled more reliably
32
+ - Fix: Container reuse logic is more accurate
33
+ - Fix: Zero-size layouts are handled better in the old architecture
34
+
35
+ ## 1.0.3
36
+ - Fix: Items that are larger than the estimated size are handled correctly
37
+
38
+ ## 1.0.2
39
+ - Fix: Initial layout works better in the old architecture
40
+ - Fix: Average size calculations are more accurate for bidirectional scrolling
41
+ - Fix: Initial scroll index behavior is more precise
42
+ - Fix: Item size calculations are more accurate overall
43
+
44
+ ## 1.0.1
45
+ - Fix: Total size calculations are correct when using average sizes
46
+ - Fix: Keyboard avoiding behavior is improved for a smoother experience
47
+
48
+ ## 1.0.0
3
49
  Initial release! Major changes if you're coming from a beta version:
4
50
 
5
- - Item hooks like `useRecyclingState` are no longer render props, but can be imported from `@legendapp/list`
51
+ - Item hooks like `useRecyclingState` are no longer render props, but can be imported directly from `@legendapp/list`.
package/index.js CHANGED
@@ -80,12 +80,6 @@ function createSelectorFunctionsArr(ctx, signalNames) {
80
80
  }
81
81
  };
82
82
  }
83
- function useArr$(signalNames) {
84
- const ctx = React2__namespace.useContext(ContextState);
85
- const { subscribe, get } = React2__namespace.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
86
- const value = shim.useSyncExternalStore(subscribe, get);
87
- return value;
88
- }
89
83
  function listen$(ctx, signalName, cb) {
90
84
  const { listeners } = ctx;
91
85
  let setListeners = listeners.get(signalName);
@@ -120,6 +114,12 @@ function getContentSize(ctx) {
120
114
  const totalSize = values.get("totalSize") || 0;
121
115
  return headerSize + footerSize + totalSize + stylePaddingTop;
122
116
  }
117
+ function useArr$(signalNames) {
118
+ const ctx = React2__namespace.useContext(ContextState);
119
+ const { subscribe, get } = React2__namespace.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
120
+ const value = shim.useSyncExternalStore(subscribe, get);
121
+ return value;
122
+ }
123
123
 
124
124
  // src/DebugView.tsx
125
125
  var DebugRow = ({ children }) => {
@@ -1546,8 +1546,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1546
1546
  maxSizeInRow = 0;
1547
1547
  }
1548
1548
  }
1549
- const prevStartBuffered = state.startBuffered;
1550
- const prevEndBuffered = state.endBuffered;
1551
1549
  Object.assign(state, {
1552
1550
  startBuffered,
1553
1551
  startBufferedId,
@@ -1564,34 +1562,19 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1564
1562
  if (startBuffered !== null && endBuffered !== null) {
1565
1563
  let numContainers = prevNumContainers;
1566
1564
  const needNewContainers = [];
1567
- if (isReset || startBuffered < prevStartBuffered || endBuffered > prevEndBuffered) {
1568
- const isContained = (i) => {
1569
- const id = getId(i);
1570
- for (let j = 0; j < numContainers; j++) {
1571
- const key = peek$(ctx, `containerItemKey${j}`);
1572
- if (key === id) {
1573
- return true;
1574
- }
1575
- }
1576
- };
1577
- if (isReset) {
1578
- for (let i = startBuffered; i <= endBuffered; i++) {
1579
- if (!isContained(i)) {
1580
- needNewContainers.push(i);
1581
- }
1582
- }
1583
- } else {
1584
- for (let i = startBuffered; i < prevStartBuffered; i++) {
1585
- if (!isContained(i)) {
1586
- needNewContainers.push(i);
1587
- }
1588
- }
1589
- for (let i = Math.max(prevEndBuffered + 1, startBuffered); i <= endBuffered; i++) {
1590
- if (!isContained(i)) {
1591
- needNewContainers.push(i);
1592
- }
1565
+ const isContained = (i) => {
1566
+ const id = getId(i);
1567
+ for (let j = 0; j < numContainers; j++) {
1568
+ const key = peek$(ctx, `containerItemKey${j}`);
1569
+ if (key === id) {
1570
+ return true;
1593
1571
  }
1594
1572
  }
1573
+ };
1574
+ for (let i = startBuffered; i <= endBuffered; i++) {
1575
+ if (!isContained(i)) {
1576
+ needNewContainers.push(i);
1577
+ }
1595
1578
  }
1596
1579
  if (needNewContainers.length > 0) {
1597
1580
  const availableContainers = findAvailableContainers(
@@ -1710,6 +1693,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1710
1693
  if (state) {
1711
1694
  state.scrollingToOffset = void 0;
1712
1695
  state.scrollAdjustHandler.setDisableAdjust(false);
1696
+ state.scrollHistory.length = 0;
1713
1697
  calculateItemsInView();
1714
1698
  }
1715
1699
  };
@@ -1717,6 +1701,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1717
1701
  var _a;
1718
1702
  const state = refState.current;
1719
1703
  state.scrollAdjustHandler.setDisableAdjust(true);
1704
+ state.scrollHistory.length = 0;
1720
1705
  state.scrollingToOffset = offset;
1721
1706
  (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
1722
1707
  x: horizontal ? offset : 0,
@@ -1987,7 +1972,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1987
1972
  }
1988
1973
  if (__DEV__ && numContainers + stillNeeded > peek$(ctx, "numContainersPooled")) {
1989
1974
  console.warn(
1990
- "[legend-list] No container to recycle, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize or increasing initialContainerPoolRatio."
1975
+ "[legend-list] No unused container available, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize or increasing initialContainerPoolRatio.",
1976
+ {
1977
+ debugInfo: {
1978
+ numContainers,
1979
+ numNeeded,
1980
+ stillNeeded,
1981
+ numContainersPooled: peek$(ctx, "numContainersPooled")
1982
+ }
1983
+ }
1991
1984
  );
1992
1985
  }
1993
1986
  }
@@ -2240,11 +2233,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2240
2233
  return;
2241
2234
  }
2242
2235
  const state = refState.current;
2236
+ const scrollingToOffset = state.scrollingToOffset;
2243
2237
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
2244
2238
  if (state.ignoreScrollFromCalcTotal && newScroll !== 0) {
2245
2239
  return;
2246
2240
  }
2247
- if (state.scrollingToOffset !== void 0 && Math.abs(newScroll - state.scrollingToOffset) < 10) {
2241
+ if (scrollingToOffset !== void 0 && Math.abs(newScroll - scrollingToOffset) < 10) {
2248
2242
  finishScrollTo();
2249
2243
  }
2250
2244
  if (state.disableScrollJumpsFrom !== void 0) {
@@ -2257,7 +2251,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2257
2251
  state.hasScrolled = true;
2258
2252
  state.lastBatchingAction = Date.now();
2259
2253
  const currentTime = performance.now();
2260
- if (!(state.scrollHistory.length === 0 && newScroll === initialContentOffset)) {
2254
+ if (scrollingToOffset === void 0 && !(state.scrollHistory.length === 0 && newScroll === initialContentOffset)) {
2261
2255
  state.scrollHistory.push({ scroll: newScroll, time: currentTime });
2262
2256
  }
2263
2257
  if (state.scrollHistory.length > 5) {
@@ -2272,10 +2266,19 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2272
2266
  let velocity = 0;
2273
2267
  if (state.scrollHistory.length >= 2) {
2274
2268
  const newest = state.scrollHistory[state.scrollHistory.length - 1];
2275
- const oldest = state.scrollHistory[0];
2276
- const scrollDiff = newest.scroll - oldest.scroll;
2277
- const timeDiff = newest.time - oldest.time;
2278
- velocity = timeDiff > 0 ? scrollDiff / timeDiff : 0;
2269
+ let oldest;
2270
+ for (let i = 0; i < state.scrollHistory.length - 1; i++) {
2271
+ const entry = state.scrollHistory[i];
2272
+ if (newest.time - entry.time <= 100) {
2273
+ oldest = entry;
2274
+ break;
2275
+ }
2276
+ }
2277
+ if (oldest) {
2278
+ const scrollDiff = newest.scroll - oldest.scroll;
2279
+ const timeDiff = newest.time - oldest.time;
2280
+ velocity = timeDiff > 0 ? scrollDiff / timeDiff : 0;
2281
+ }
2279
2282
  }
2280
2283
  state.scrollPrev = state.scroll;
2281
2284
  state.scrollPrevTime = state.scrollTime;
package/index.mjs CHANGED
@@ -59,12 +59,6 @@ function createSelectorFunctionsArr(ctx, signalNames) {
59
59
  }
60
60
  };
61
61
  }
62
- function useArr$(signalNames) {
63
- const ctx = React2.useContext(ContextState);
64
- const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
65
- const value = useSyncExternalStore(subscribe, get);
66
- return value;
67
- }
68
62
  function listen$(ctx, signalName, cb) {
69
63
  const { listeners } = ctx;
70
64
  let setListeners = listeners.get(signalName);
@@ -99,6 +93,12 @@ function getContentSize(ctx) {
99
93
  const totalSize = values.get("totalSize") || 0;
100
94
  return headerSize + footerSize + totalSize + stylePaddingTop;
101
95
  }
96
+ function useArr$(signalNames) {
97
+ const ctx = React2.useContext(ContextState);
98
+ const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
99
+ const value = useSyncExternalStore(subscribe, get);
100
+ return value;
101
+ }
102
102
 
103
103
  // src/DebugView.tsx
104
104
  var DebugRow = ({ children }) => {
@@ -1525,8 +1525,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1525
1525
  maxSizeInRow = 0;
1526
1526
  }
1527
1527
  }
1528
- const prevStartBuffered = state.startBuffered;
1529
- const prevEndBuffered = state.endBuffered;
1530
1528
  Object.assign(state, {
1531
1529
  startBuffered,
1532
1530
  startBufferedId,
@@ -1543,34 +1541,19 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1543
1541
  if (startBuffered !== null && endBuffered !== null) {
1544
1542
  let numContainers = prevNumContainers;
1545
1543
  const needNewContainers = [];
1546
- if (isReset || startBuffered < prevStartBuffered || endBuffered > prevEndBuffered) {
1547
- const isContained = (i) => {
1548
- const id = getId(i);
1549
- for (let j = 0; j < numContainers; j++) {
1550
- const key = peek$(ctx, `containerItemKey${j}`);
1551
- if (key === id) {
1552
- return true;
1553
- }
1554
- }
1555
- };
1556
- if (isReset) {
1557
- for (let i = startBuffered; i <= endBuffered; i++) {
1558
- if (!isContained(i)) {
1559
- needNewContainers.push(i);
1560
- }
1561
- }
1562
- } else {
1563
- for (let i = startBuffered; i < prevStartBuffered; i++) {
1564
- if (!isContained(i)) {
1565
- needNewContainers.push(i);
1566
- }
1567
- }
1568
- for (let i = Math.max(prevEndBuffered + 1, startBuffered); i <= endBuffered; i++) {
1569
- if (!isContained(i)) {
1570
- needNewContainers.push(i);
1571
- }
1544
+ const isContained = (i) => {
1545
+ const id = getId(i);
1546
+ for (let j = 0; j < numContainers; j++) {
1547
+ const key = peek$(ctx, `containerItemKey${j}`);
1548
+ if (key === id) {
1549
+ return true;
1572
1550
  }
1573
1551
  }
1552
+ };
1553
+ for (let i = startBuffered; i <= endBuffered; i++) {
1554
+ if (!isContained(i)) {
1555
+ needNewContainers.push(i);
1556
+ }
1574
1557
  }
1575
1558
  if (needNewContainers.length > 0) {
1576
1559
  const availableContainers = findAvailableContainers(
@@ -1689,6 +1672,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1689
1672
  if (state) {
1690
1673
  state.scrollingToOffset = void 0;
1691
1674
  state.scrollAdjustHandler.setDisableAdjust(false);
1675
+ state.scrollHistory.length = 0;
1692
1676
  calculateItemsInView();
1693
1677
  }
1694
1678
  };
@@ -1696,6 +1680,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1696
1680
  var _a;
1697
1681
  const state = refState.current;
1698
1682
  state.scrollAdjustHandler.setDisableAdjust(true);
1683
+ state.scrollHistory.length = 0;
1699
1684
  state.scrollingToOffset = offset;
1700
1685
  (_a = refScroller.current) == null ? void 0 : _a.scrollTo({
1701
1686
  x: horizontal ? offset : 0,
@@ -1966,7 +1951,15 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1966
1951
  }
1967
1952
  if (__DEV__ && numContainers + stillNeeded > peek$(ctx, "numContainersPooled")) {
1968
1953
  console.warn(
1969
- "[legend-list] No container to recycle, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize or increasing initialContainerPoolRatio."
1954
+ "[legend-list] No unused container available, so creating one on demand. This can be a minor performance issue and is likely caused by the estimatedItemSize being too large. Consider decreasing estimatedItemSize or increasing initialContainerPoolRatio.",
1955
+ {
1956
+ debugInfo: {
1957
+ numContainers,
1958
+ numNeeded,
1959
+ stillNeeded,
1960
+ numContainersPooled: peek$(ctx, "numContainersPooled")
1961
+ }
1962
+ }
1970
1963
  );
1971
1964
  }
1972
1965
  }
@@ -2219,11 +2212,12 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2219
2212
  return;
2220
2213
  }
2221
2214
  const state = refState.current;
2215
+ const scrollingToOffset = state.scrollingToOffset;
2222
2216
  const newScroll = event.nativeEvent.contentOffset[horizontal ? "x" : "y"];
2223
2217
  if (state.ignoreScrollFromCalcTotal && newScroll !== 0) {
2224
2218
  return;
2225
2219
  }
2226
- if (state.scrollingToOffset !== void 0 && Math.abs(newScroll - state.scrollingToOffset) < 10) {
2220
+ if (scrollingToOffset !== void 0 && Math.abs(newScroll - scrollingToOffset) < 10) {
2227
2221
  finishScrollTo();
2228
2222
  }
2229
2223
  if (state.disableScrollJumpsFrom !== void 0) {
@@ -2236,7 +2230,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2236
2230
  state.hasScrolled = true;
2237
2231
  state.lastBatchingAction = Date.now();
2238
2232
  const currentTime = performance.now();
2239
- if (!(state.scrollHistory.length === 0 && newScroll === initialContentOffset)) {
2233
+ if (scrollingToOffset === void 0 && !(state.scrollHistory.length === 0 && newScroll === initialContentOffset)) {
2240
2234
  state.scrollHistory.push({ scroll: newScroll, time: currentTime });
2241
2235
  }
2242
2236
  if (state.scrollHistory.length > 5) {
@@ -2251,10 +2245,19 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
2251
2245
  let velocity = 0;
2252
2246
  if (state.scrollHistory.length >= 2) {
2253
2247
  const newest = state.scrollHistory[state.scrollHistory.length - 1];
2254
- const oldest = state.scrollHistory[0];
2255
- const scrollDiff = newest.scroll - oldest.scroll;
2256
- const timeDiff = newest.time - oldest.time;
2257
- velocity = timeDiff > 0 ? scrollDiff / timeDiff : 0;
2248
+ let oldest;
2249
+ for (let i = 0; i < state.scrollHistory.length - 1; i++) {
2250
+ const entry = state.scrollHistory[i];
2251
+ if (newest.time - entry.time <= 100) {
2252
+ oldest = entry;
2253
+ break;
2254
+ }
2255
+ }
2256
+ if (oldest) {
2257
+ const scrollDiff = newest.scroll - oldest.scroll;
2258
+ const timeDiff = newest.time - oldest.time;
2259
+ velocity = timeDiff > 0 ? scrollDiff / timeDiff : 0;
2260
+ }
2258
2261
  }
2259
2262
  state.scrollPrev = state.scroll;
2260
2263
  state.scrollPrevTime = state.scrollTime;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Legend List is a drop-in replacement for FlatList with much better performance and supporting dynamically sized items.",
5
5
  "sideEffects": false,
6
6
  "private": false,