@legendapp/list 1.0.6 → 1.0.8

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 (3) hide show
  1. package/index.js +200 -103
  2. package/index.mjs +200 -103
  3. package/package.json +1 -1
package/index.js CHANGED
@@ -46,15 +46,42 @@ function StateProvider({ children }) {
46
46
  function useStateContext() {
47
47
  return React2__namespace.useContext(ContextState);
48
48
  }
49
- function createSelectorFunctions(ctx, signalName) {
49
+ function createSelectorFunctionsArr(ctx, signalNames) {
50
+ let lastValues = [];
51
+ let lastSignalValues = [];
50
52
  return {
51
- subscribe: (cb) => listen$(ctx, signalName, cb),
52
- get: () => peek$(ctx, signalName)
53
+ subscribe: (cb) => {
54
+ const listeners = [];
55
+ for (const signalName of signalNames) {
56
+ listeners.push(listen$(ctx, signalName, cb));
57
+ }
58
+ return () => {
59
+ for (const listener of listeners) {
60
+ listener();
61
+ }
62
+ };
63
+ },
64
+ get: () => {
65
+ const currentValues = [];
66
+ let hasChanged = false;
67
+ for (let i = 0; i < signalNames.length; i++) {
68
+ const value = peek$(ctx, signalNames[i]);
69
+ currentValues.push(value);
70
+ if (value !== lastSignalValues[i]) {
71
+ hasChanged = true;
72
+ }
73
+ }
74
+ lastSignalValues = currentValues;
75
+ if (hasChanged) {
76
+ lastValues = currentValues;
77
+ }
78
+ return lastValues;
79
+ }
53
80
  };
54
81
  }
55
- function use$(signalName) {
82
+ function useArr$(signalNames) {
56
83
  const ctx = React2__namespace.useContext(ContextState);
57
- const { subscribe, get } = React2__namespace.useMemo(() => createSelectorFunctions(ctx, signalName), []);
84
+ const { subscribe, get } = React2__namespace.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
58
85
  const value = React2.useSyncExternalStore(subscribe, get);
59
86
  return value;
60
87
  }
@@ -99,15 +126,25 @@ var DebugRow = ({ children }) => {
99
126
  };
100
127
  var DebugView = React2__namespace.memo(function DebugView2({ state }) {
101
128
  const ctx = useStateContext();
102
- const totalSize = use$("totalSize") || 0;
103
- const totalSizeWithScrollAdjust = use$("totalSizeWithScrollAdjust") || 0;
104
- const scrollAdjust = use$("scrollAdjust") || 0;
105
- const rawScroll = use$("debugRawScroll") || 0;
106
- const scroll = use$("debugComputedScroll") || 0;
129
+ const [
130
+ totalSize = 0,
131
+ totalSizeWithScrollAdjust = 0,
132
+ scrollAdjust = 0,
133
+ rawScroll = 0,
134
+ scroll = 0,
135
+ numContainers = 0,
136
+ numContainersPooled = 0
137
+ ] = useArr$([
138
+ "totalSize",
139
+ "totalSizeWithScrollAdjust",
140
+ "scrollAdjust",
141
+ "debugRawScroll",
142
+ "debugComputedScroll",
143
+ "numContainers",
144
+ "numContainersPooled"
145
+ ]);
107
146
  const contentSize = getContentSize(ctx);
108
147
  const [, forceUpdate] = React2.useReducer((x) => x + 1, 0);
109
- use$("numContainers");
110
- use$("numContainersPooled");
111
148
  useInterval(() => {
112
149
  forceUpdate();
113
150
  }, 100);
@@ -162,6 +199,12 @@ function roundSize(size) {
162
199
  function isNullOrUndefined(value) {
163
200
  return value === null || value === void 0;
164
201
  }
202
+ function comparatorByDistance(a, b) {
203
+ return b.distance - a.distance;
204
+ }
205
+ function comparatorDefault(a, b) {
206
+ return a - b;
207
+ }
165
208
  var symbolFirst = Symbol();
166
209
  function useInit(cb) {
167
210
  const refValue = React2.useRef(symbolFirst);
@@ -285,14 +328,25 @@ var Container = ({
285
328
  }) => {
286
329
  const ctx = useStateContext();
287
330
  const columnWrapperStyle = ctx.columnWrapperStyle;
288
- const maintainVisibleContentPosition = use$("maintainVisibleContentPosition");
289
- const position = use$(`containerPosition${id}`) || ANCHORED_POSITION_OUT_OF_VIEW;
290
- const column = use$(`containerColumn${id}`) || 0;
291
- const numColumns = use$("numColumns");
292
- const lastItemKeys = use$("lastItemKeys");
293
- const itemKey = use$(`containerItemKey${id}`);
294
- const data = use$(`containerItemData${id}`);
295
- const extraData = use$("extraData");
331
+ const [
332
+ maintainVisibleContentPosition,
333
+ position = ANCHORED_POSITION_OUT_OF_VIEW,
334
+ column = 0,
335
+ numColumns,
336
+ lastItemKeys,
337
+ itemKey,
338
+ data,
339
+ extraData
340
+ ] = useArr$([
341
+ "maintainVisibleContentPosition",
342
+ `containerPosition${id}`,
343
+ `containerColumn${id}`,
344
+ "numColumns",
345
+ "lastItemKeys",
346
+ `containerItemKey${id}`,
347
+ `containerItemData${id}`,
348
+ "extraData"
349
+ ]);
296
350
  const refLastSize = React2.useRef();
297
351
  const ref = React2.useRef(null);
298
352
  const [layoutRenderCount, forceLayoutRender] = React2.useState(0);
@@ -390,11 +444,11 @@ var Container = ({
390
444
  const contentFragment = /* @__PURE__ */ React2__namespace.default.createElement(React2__namespace.default.Fragment, { key: recycleItems ? void 0 : itemKey }, /* @__PURE__ */ React2__namespace.default.createElement(ContextContainer.Provider, { value: contextValue }, renderedItem, renderedItemInfo && ItemSeparatorComponent && !lastItemKeys.includes(itemKey) && /* @__PURE__ */ React2__namespace.default.createElement(ItemSeparatorComponent, { leadingItem: renderedItemInfo.item })));
391
445
  if (maintainVisibleContentPosition) {
392
446
  const anchorStyle = position.type === "top" ? { position: "absolute", top: 0, left: 0, right: 0 } : { position: "absolute", bottom: 0, left: 0, right: 0 };
393
- if (ENABLE_DEVMODE) {
447
+ if (__DEV__ && ENABLE_DEVMODE) {
394
448
  anchorStyle.borderColor = position.type === "top" ? "red" : "blue";
395
449
  anchorStyle.borderWidth = 1;
396
450
  }
397
- return /* @__PURE__ */ React2__namespace.default.createElement(LeanView, { style }, /* @__PURE__ */ React2__namespace.default.createElement(LeanView, { style: anchorStyle, onLayout, ref }, contentFragment, ENABLE_DEVMODE && /* @__PURE__ */ React2__namespace.default.createElement(reactNative.Text, { style: { position: "absolute", top: 0, left: 0, zIndex: 1e3 } }, position.top)));
451
+ return /* @__PURE__ */ React2__namespace.default.createElement(LeanView, { style }, /* @__PURE__ */ React2__namespace.default.createElement(LeanView, { style: anchorStyle, onLayout, ref }, contentFragment, __DEV__ && ENABLE_DEVMODE && /* @__PURE__ */ React2__namespace.default.createElement(reactNative.Text, { style: { position: "absolute", top: 0, left: 0, zIndex: 1e3 } }, position.top)));
398
452
  }
399
453
  return /* @__PURE__ */ React2__namespace.default.createElement(LeanView, { style, onLayout, ref }, contentFragment);
400
454
  };
@@ -438,7 +492,7 @@ var Containers = typedMemo(function Containers2({
438
492
  }) {
439
493
  const ctx = useStateContext();
440
494
  const columnWrapperStyle = ctx.columnWrapperStyle;
441
- const numContainers = use$("numContainersPooled");
495
+ const [numContainers] = useArr$(["numContainersPooled"]);
442
496
  const animSize = useValue$(
443
497
  "totalSizeWithScrollAdjust",
444
498
  void 0,
@@ -1035,10 +1089,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1035
1089
  isAtTop: false,
1036
1090
  data: dataProp,
1037
1091
  scrollLength: initialScrollLength,
1038
- startBuffered: 0,
1039
- startNoBuffer: 0,
1040
- endBuffered: 0,
1041
- endNoBuffer: 0,
1092
+ startBuffered: -1,
1093
+ startNoBuffer: -1,
1094
+ endBuffered: -1,
1095
+ endNoBuffer: -1,
1042
1096
  scroll: initialContentOffset || 0,
1043
1097
  totalSize: 0,
1044
1098
  totalSizeBelowAnchor: 0,
@@ -1353,14 +1407,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1353
1407
  const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
1354
1408
  const numColumns = peek$(ctx, "numColumns");
1355
1409
  const previousScrollAdjust = scrollAdjustHandler.getAppliedAdjust();
1356
- const scrollExtra = Math.max(-16, Math.min(16, speed)) * 16;
1357
1410
  let scrollState = state.scroll;
1411
+ const scrollExtra = Math.max(-16, Math.min(16, speed)) * 24;
1358
1412
  const useAverageSize = !state.disableScrollJumpsFrom && speed >= 0 && peek$(ctx, "containersDidLayout");
1359
1413
  if (!state.queuedInitialLayout && initialScrollIndex) {
1360
1414
  const updatedOffset = calculateOffsetForIndex(initialScrollIndex);
1361
1415
  scrollState = updatedOffset;
1362
1416
  }
1363
- let scroll = scrollState - previousScrollAdjust - topPad;
1417
+ let scroll = scrollState + scrollExtra - previousScrollAdjust - topPad;
1364
1418
  if (scroll + scrollLength > totalSize) {
1365
1419
  scroll = totalSize - scrollLength;
1366
1420
  }
@@ -1370,13 +1424,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1370
1424
  }
1371
1425
  let scrollBufferTop = scrollBuffer;
1372
1426
  let scrollBufferBottom = scrollBuffer;
1373
- if (scrollExtra > 8) {
1374
- scrollBufferTop = 0;
1375
- scrollBufferBottom = scrollBuffer + scrollExtra;
1427
+ if (speed > 0) {
1428
+ scrollBufferTop = scrollBuffer * 0.1;
1429
+ scrollBufferBottom = scrollBuffer * 1.9;
1376
1430
  }
1377
- if (scrollExtra < -8) {
1378
- scrollBufferTop = scrollBuffer - scrollExtra;
1379
- scrollBufferBottom = 0;
1431
+ if (speed < 0) {
1432
+ scrollBufferTop = scrollBuffer * 1.9;
1433
+ scrollBufferBottom = scrollBuffer * 0.1;
1380
1434
  }
1381
1435
  if (state.scrollForNextCalculateItemsInView) {
1382
1436
  const { top: top2, bottom } = state.scrollForNextCalculateItemsInView;
@@ -1385,7 +1439,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1385
1439
  }
1386
1440
  }
1387
1441
  const scrollBottom = scroll + scrollLength;
1388
- const prevEndBuffered = state.endBuffered;
1389
1442
  let startNoBuffer = null;
1390
1443
  let startBuffered = null;
1391
1444
  let startBufferedId = null;
@@ -1437,7 +1490,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1437
1490
  return topOffset;
1438
1491
  };
1439
1492
  let foundEnd = false;
1440
- for (let i = Math.max(0, loopStart); i < data.length && (!foundEnd || i <= prevEndBuffered); i++) {
1493
+ let lastSize;
1494
+ const prevNumContainers = ctx.values.get("numContainers");
1495
+ let maxIndexRendered = 0;
1496
+ for (let i = 0; i < prevNumContainers; i++) {
1497
+ const key = peek$(ctx, `containerItemKey${i}`);
1498
+ if (key !== void 0) {
1499
+ const index = state.indexByKey.get(key);
1500
+ maxIndexRendered = Math.max(maxIndexRendered, index);
1501
+ }
1502
+ }
1503
+ for (let i = Math.max(0, loopStart); i < data.length && (!foundEnd || i <= maxIndexRendered); i++) {
1441
1504
  const id = getId(i);
1442
1505
  const size = getItemSize(id, i, data[i], useAverageSize);
1443
1506
  maxSizeInRow = Math.max(maxSizeInRow, size);
@@ -1464,6 +1527,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1464
1527
  }
1465
1528
  if (top <= scrollBottom + scrollBufferBottom) {
1466
1529
  endBuffered = i;
1530
+ lastSize = maxSizeInRow;
1467
1531
  } else {
1468
1532
  foundEnd = true;
1469
1533
  }
@@ -1476,6 +1540,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1476
1540
  maxSizeInRow = 0;
1477
1541
  }
1478
1542
  }
1543
+ const prevStartBuffered = state.startBuffered;
1544
+ const prevEndBuffered = state.endBuffered;
1479
1545
  Object.assign(state, {
1480
1546
  startBuffered,
1481
1547
  startBufferedId,
@@ -1483,83 +1549,64 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1483
1549
  endBuffered,
1484
1550
  endNoBuffer
1485
1551
  });
1486
- const nextTop = Math.ceil(startBuffered !== null ? positions.get(startBufferedId) + scrollBuffer : 0);
1487
- const nextBottom = Math.floor(
1488
- endBuffered !== null ? (positions.get(getId(endBuffered + 1)) || 0) - scrollLength - scrollBuffer : 0
1489
- );
1490
- if (state.enableScrollForNextCalculateItemsInView) {
1552
+ if (state.enableScrollForNextCalculateItemsInView && lastSize) {
1553
+ const aboveFirst = startBuffered - 1;
1554
+ let nextTop = 0;
1555
+ if (aboveFirst >= 0) {
1556
+ const aboveFirstSize = getItemSize(getId(aboveFirst), aboveFirst, data[aboveFirst], useAverageSize);
1557
+ nextTop = scroll - aboveFirstSize;
1558
+ }
1559
+ const nextBottom = scroll + lastSize;
1491
1560
  state.scrollForNextCalculateItemsInView = nextTop >= 0 && nextBottom >= 0 ? {
1492
1561
  top: nextTop,
1493
1562
  bottom: nextBottom
1494
1563
  } : void 0;
1495
1564
  }
1496
1565
  if (startBuffered !== null && endBuffered !== null) {
1497
- const prevNumContainers = ctx.values.get("numContainers");
1498
1566
  let numContainers = prevNumContainers;
1499
- let didWarnMoreContainers = false;
1500
- const allocatedContainers = /* @__PURE__ */ new Set();
1501
- for (let i = startBuffered; i <= endBuffered; i++) {
1502
- let isContained = false;
1503
- const id = getId(i);
1504
- for (let j = 0; j < numContainers; j++) {
1505
- const key = peek$(ctx, `containerItemKey${j}`);
1506
- if (key === id) {
1507
- isContained = true;
1508
- break;
1509
- }
1510
- }
1511
- if (!isContained) {
1512
- const top2 = positions.get(id) || 0;
1513
- let furthestIndex = -1;
1514
- let furthestDistance = 0;
1515
- for (let u = 0; u < numContainers; u++) {
1516
- if (allocatedContainers.has(u)) {
1517
- continue;
1518
- }
1519
- const key = peek$(ctx, `containerItemKey${u}`);
1520
- if (key === void 0) {
1521
- furthestIndex = u;
1522
- break;
1523
- }
1524
- const index2 = state.indexByKey.get(key);
1525
- const pos = peek$(ctx, `containerPosition${u}`).top;
1526
- if (isNullOrUndefined(index2) || pos === POSITION_OUT_OF_VIEW) {
1527
- furthestIndex = u;
1528
- break;
1529
- }
1530
- if (index2 < startBuffered || index2 > endBuffered) {
1531
- const distance = Math.abs(pos - top2);
1532
- if (index2 < 0 || pos === POSITION_OUT_OF_VIEW || distance > furthestDistance) {
1533
- furthestDistance = distance;
1534
- furthestIndex = u;
1535
- }
1567
+ const needNewContainers = [];
1568
+ if (startBuffered < prevStartBuffered || endBuffered > prevEndBuffered) {
1569
+ const isContained = (i) => {
1570
+ const id = getId(i);
1571
+ for (let j = 0; j < numContainers; j++) {
1572
+ const key = peek$(ctx, `containerItemKey${j}`);
1573
+ if (key === id) {
1574
+ return true;
1536
1575
  }
1537
1576
  }
1538
- const containerId = furthestIndex >= 0 ? furthestIndex : numContainers;
1539
- set$(ctx, `containerItemKey${containerId}`, id);
1540
- const index = state.indexByKey.get(id);
1541
- set$(ctx, `containerItemData${containerId}`, data[index]);
1542
- allocatedContainers.add(containerId);
1543
- if (furthestIndex === -1) {
1544
- numContainers++;
1545
- set$(ctx, `containerItemKey${containerId}`, id);
1546
- const index2 = state.indexByKey.get(id);
1547
- set$(ctx, `containerItemData${containerId}`, data[index2]);
1548
- set$(ctx, `containerPosition${containerId}`, ANCHORED_POSITION_OUT_OF_VIEW);
1549
- set$(ctx, `containerColumn${containerId}`, -1);
1550
- if (__DEV__ && !didWarnMoreContainers && numContainers > peek$(ctx, "numContainersPooled")) {
1551
- didWarnMoreContainers = true;
1552
- console.warn(
1553
- "[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."
1554
- );
1555
- }
1577
+ };
1578
+ for (let i = startBuffered; i < prevStartBuffered; i++) {
1579
+ if (!isContained(i)) {
1580
+ needNewContainers.push(i);
1581
+ }
1582
+ }
1583
+ for (let i = Math.max(prevEndBuffered + 1, startBuffered); i <= endBuffered; i++) {
1584
+ if (!isContained(i)) {
1585
+ needNewContainers.push(i);
1556
1586
  }
1557
1587
  }
1558
1588
  }
1559
- if (numContainers !== prevNumContainers) {
1560
- set$(ctx, "numContainers", numContainers);
1561
- if (numContainers > peek$(ctx, "numContainersPooled")) {
1562
- set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
1589
+ if (needNewContainers.length > 0) {
1590
+ const availableContainers = findAvailableContainers(
1591
+ needNewContainers.length,
1592
+ startBuffered,
1593
+ endBuffered
1594
+ );
1595
+ for (let idx = 0; idx < needNewContainers.length; idx++) {
1596
+ const i = needNewContainers[idx];
1597
+ const containerIndex = availableContainers[idx];
1598
+ const id = getId(i);
1599
+ set$(ctx, `containerItemKey${containerIndex}`, id);
1600
+ set$(ctx, `containerItemData${containerIndex}`, data[i]);
1601
+ if (containerIndex >= numContainers) {
1602
+ numContainers = containerIndex + 1;
1603
+ }
1604
+ }
1605
+ if (numContainers !== prevNumContainers) {
1606
+ set$(ctx, "numContainers", numContainers);
1607
+ if (numContainers > peek$(ctx, "numContainersPooled")) {
1608
+ set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
1609
+ }
1563
1610
  }
1564
1611
  }
1565
1612
  for (let i = 0; i < numContainers; i++) {
@@ -1589,7 +1636,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1589
1636
  const prevPos = peek$(ctx, `containerPosition${i}`);
1590
1637
  const prevColumn = peek$(ctx, `containerColumn${i}`);
1591
1638
  const prevData = peek$(ctx, `containerItemData${i}`);
1592
- if (pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1639
+ if (!prevPos || pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1593
1640
  set$(ctx, `containerPosition${i}`, pos);
1594
1641
  }
1595
1642
  if (column2 >= 0 && column2 !== prevColumn) {
@@ -1881,6 +1928,56 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1881
1928
  });
1882
1929
  addTotalSize(null, totalSize, totalSizeBelowIndex);
1883
1930
  };
1931
+ const findAvailableContainers = (numNeeded, startBuffered, endBuffered) => {
1932
+ const state = refState.current;
1933
+ const numContainers = peek$(ctx, "numContainers");
1934
+ if (numNeeded === 0) return [];
1935
+ const result = [];
1936
+ const availableContainers = [];
1937
+ for (let u = 0; u < numContainers; u++) {
1938
+ const key = peek$(ctx, `containerItemKey${u}`);
1939
+ if (key === void 0) {
1940
+ result.push(u);
1941
+ if (result.length >= numNeeded) {
1942
+ return result;
1943
+ }
1944
+ }
1945
+ }
1946
+ for (let u = 0; u < numContainers; u++) {
1947
+ const key = peek$(ctx, `containerItemKey${u}`);
1948
+ if (key === void 0) continue;
1949
+ const index = state.indexByKey.get(key);
1950
+ if (index < startBuffered) {
1951
+ availableContainers.push({ index: u, distance: startBuffered - index });
1952
+ } else if (index > endBuffered) {
1953
+ availableContainers.push({ index: u, distance: index - endBuffered });
1954
+ }
1955
+ }
1956
+ const remaining = numNeeded - result.length;
1957
+ if (remaining > 0) {
1958
+ if (availableContainers.length > 0) {
1959
+ if (availableContainers.length > remaining) {
1960
+ availableContainers.sort(comparatorByDistance);
1961
+ availableContainers.length = remaining;
1962
+ }
1963
+ for (const container of availableContainers) {
1964
+ result.push(container.index);
1965
+ }
1966
+ }
1967
+ const stillNeeded = numNeeded - result.length;
1968
+ if (stillNeeded > 0) {
1969
+ for (let i = 0; i < stillNeeded; i++) {
1970
+ result.push(numContainers + i);
1971
+ }
1972
+ if (__DEV__ && numContainers + stillNeeded > peek$(ctx, "numContainersPooled")) {
1973
+ console.warn(
1974
+ "[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
+ );
1976
+ }
1977
+ }
1978
+ }
1979
+ return result.sort(comparatorDefault);
1980
+ };
1884
1981
  const isFirst = !refState.current.renderItem;
1885
1982
  const memoizedLastItemKeys = React2.useMemo(() => {
1886
1983
  if (!dataProp.length) return [];
package/index.mjs CHANGED
@@ -25,15 +25,42 @@ function StateProvider({ children }) {
25
25
  function useStateContext() {
26
26
  return React2.useContext(ContextState);
27
27
  }
28
- function createSelectorFunctions(ctx, signalName) {
28
+ function createSelectorFunctionsArr(ctx, signalNames) {
29
+ let lastValues = [];
30
+ let lastSignalValues = [];
29
31
  return {
30
- subscribe: (cb) => listen$(ctx, signalName, cb),
31
- get: () => peek$(ctx, signalName)
32
+ subscribe: (cb) => {
33
+ const listeners = [];
34
+ for (const signalName of signalNames) {
35
+ listeners.push(listen$(ctx, signalName, cb));
36
+ }
37
+ return () => {
38
+ for (const listener of listeners) {
39
+ listener();
40
+ }
41
+ };
42
+ },
43
+ get: () => {
44
+ const currentValues = [];
45
+ let hasChanged = false;
46
+ for (let i = 0; i < signalNames.length; i++) {
47
+ const value = peek$(ctx, signalNames[i]);
48
+ currentValues.push(value);
49
+ if (value !== lastSignalValues[i]) {
50
+ hasChanged = true;
51
+ }
52
+ }
53
+ lastSignalValues = currentValues;
54
+ if (hasChanged) {
55
+ lastValues = currentValues;
56
+ }
57
+ return lastValues;
58
+ }
32
59
  };
33
60
  }
34
- function use$(signalName) {
61
+ function useArr$(signalNames) {
35
62
  const ctx = React2.useContext(ContextState);
36
- const { subscribe, get } = React2.useMemo(() => createSelectorFunctions(ctx, signalName), []);
63
+ const { subscribe, get } = React2.useMemo(() => createSelectorFunctionsArr(ctx, signalNames), [ctx, signalNames]);
37
64
  const value = useSyncExternalStore(subscribe, get);
38
65
  return value;
39
66
  }
@@ -78,15 +105,25 @@ var DebugRow = ({ children }) => {
78
105
  };
79
106
  var DebugView = React2.memo(function DebugView2({ state }) {
80
107
  const ctx = useStateContext();
81
- const totalSize = use$("totalSize") || 0;
82
- const totalSizeWithScrollAdjust = use$("totalSizeWithScrollAdjust") || 0;
83
- const scrollAdjust = use$("scrollAdjust") || 0;
84
- const rawScroll = use$("debugRawScroll") || 0;
85
- const scroll = use$("debugComputedScroll") || 0;
108
+ const [
109
+ totalSize = 0,
110
+ totalSizeWithScrollAdjust = 0,
111
+ scrollAdjust = 0,
112
+ rawScroll = 0,
113
+ scroll = 0,
114
+ numContainers = 0,
115
+ numContainersPooled = 0
116
+ ] = useArr$([
117
+ "totalSize",
118
+ "totalSizeWithScrollAdjust",
119
+ "scrollAdjust",
120
+ "debugRawScroll",
121
+ "debugComputedScroll",
122
+ "numContainers",
123
+ "numContainersPooled"
124
+ ]);
86
125
  const contentSize = getContentSize(ctx);
87
126
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
88
- use$("numContainers");
89
- use$("numContainersPooled");
90
127
  useInterval(() => {
91
128
  forceUpdate();
92
129
  }, 100);
@@ -141,6 +178,12 @@ function roundSize(size) {
141
178
  function isNullOrUndefined(value) {
142
179
  return value === null || value === void 0;
143
180
  }
181
+ function comparatorByDistance(a, b) {
182
+ return b.distance - a.distance;
183
+ }
184
+ function comparatorDefault(a, b) {
185
+ return a - b;
186
+ }
144
187
  var symbolFirst = Symbol();
145
188
  function useInit(cb) {
146
189
  const refValue = useRef(symbolFirst);
@@ -264,14 +307,25 @@ var Container = ({
264
307
  }) => {
265
308
  const ctx = useStateContext();
266
309
  const columnWrapperStyle = ctx.columnWrapperStyle;
267
- const maintainVisibleContentPosition = use$("maintainVisibleContentPosition");
268
- const position = use$(`containerPosition${id}`) || ANCHORED_POSITION_OUT_OF_VIEW;
269
- const column = use$(`containerColumn${id}`) || 0;
270
- const numColumns = use$("numColumns");
271
- const lastItemKeys = use$("lastItemKeys");
272
- const itemKey = use$(`containerItemKey${id}`);
273
- const data = use$(`containerItemData${id}`);
274
- const extraData = use$("extraData");
310
+ const [
311
+ maintainVisibleContentPosition,
312
+ position = ANCHORED_POSITION_OUT_OF_VIEW,
313
+ column = 0,
314
+ numColumns,
315
+ lastItemKeys,
316
+ itemKey,
317
+ data,
318
+ extraData
319
+ ] = useArr$([
320
+ "maintainVisibleContentPosition",
321
+ `containerPosition${id}`,
322
+ `containerColumn${id}`,
323
+ "numColumns",
324
+ "lastItemKeys",
325
+ `containerItemKey${id}`,
326
+ `containerItemData${id}`,
327
+ "extraData"
328
+ ]);
275
329
  const refLastSize = useRef();
276
330
  const ref = useRef(null);
277
331
  const [layoutRenderCount, forceLayoutRender] = useState(0);
@@ -369,11 +423,11 @@ var Container = ({
369
423
  const contentFragment = /* @__PURE__ */ React2__default.createElement(React2__default.Fragment, { key: recycleItems ? void 0 : itemKey }, /* @__PURE__ */ React2__default.createElement(ContextContainer.Provider, { value: contextValue }, renderedItem, renderedItemInfo && ItemSeparatorComponent && !lastItemKeys.includes(itemKey) && /* @__PURE__ */ React2__default.createElement(ItemSeparatorComponent, { leadingItem: renderedItemInfo.item })));
370
424
  if (maintainVisibleContentPosition) {
371
425
  const anchorStyle = position.type === "top" ? { position: "absolute", top: 0, left: 0, right: 0 } : { position: "absolute", bottom: 0, left: 0, right: 0 };
372
- if (ENABLE_DEVMODE) {
426
+ if (__DEV__ && ENABLE_DEVMODE) {
373
427
  anchorStyle.borderColor = position.type === "top" ? "red" : "blue";
374
428
  anchorStyle.borderWidth = 1;
375
429
  }
376
- return /* @__PURE__ */ React2__default.createElement(LeanView, { style }, /* @__PURE__ */ React2__default.createElement(LeanView, { style: anchorStyle, onLayout, ref }, contentFragment, ENABLE_DEVMODE && /* @__PURE__ */ React2__default.createElement(Text, { style: { position: "absolute", top: 0, left: 0, zIndex: 1e3 } }, position.top)));
430
+ return /* @__PURE__ */ React2__default.createElement(LeanView, { style }, /* @__PURE__ */ React2__default.createElement(LeanView, { style: anchorStyle, onLayout, ref }, contentFragment, __DEV__ && ENABLE_DEVMODE && /* @__PURE__ */ React2__default.createElement(Text, { style: { position: "absolute", top: 0, left: 0, zIndex: 1e3 } }, position.top)));
377
431
  }
378
432
  return /* @__PURE__ */ React2__default.createElement(LeanView, { style, onLayout, ref }, contentFragment);
379
433
  };
@@ -417,7 +471,7 @@ var Containers = typedMemo(function Containers2({
417
471
  }) {
418
472
  const ctx = useStateContext();
419
473
  const columnWrapperStyle = ctx.columnWrapperStyle;
420
- const numContainers = use$("numContainersPooled");
474
+ const [numContainers] = useArr$(["numContainersPooled"]);
421
475
  const animSize = useValue$(
422
476
  "totalSizeWithScrollAdjust",
423
477
  void 0,
@@ -1014,10 +1068,10 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1014
1068
  isAtTop: false,
1015
1069
  data: dataProp,
1016
1070
  scrollLength: initialScrollLength,
1017
- startBuffered: 0,
1018
- startNoBuffer: 0,
1019
- endBuffered: 0,
1020
- endNoBuffer: 0,
1071
+ startBuffered: -1,
1072
+ startNoBuffer: -1,
1073
+ endBuffered: -1,
1074
+ endNoBuffer: -1,
1021
1075
  scroll: initialContentOffset || 0,
1022
1076
  totalSize: 0,
1023
1077
  totalSizeBelowAnchor: 0,
@@ -1332,14 +1386,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1332
1386
  const topPad = peek$(ctx, "stylePaddingTop") + peek$(ctx, "headerSize");
1333
1387
  const numColumns = peek$(ctx, "numColumns");
1334
1388
  const previousScrollAdjust = scrollAdjustHandler.getAppliedAdjust();
1335
- const scrollExtra = Math.max(-16, Math.min(16, speed)) * 16;
1336
1389
  let scrollState = state.scroll;
1390
+ const scrollExtra = Math.max(-16, Math.min(16, speed)) * 24;
1337
1391
  const useAverageSize = !state.disableScrollJumpsFrom && speed >= 0 && peek$(ctx, "containersDidLayout");
1338
1392
  if (!state.queuedInitialLayout && initialScrollIndex) {
1339
1393
  const updatedOffset = calculateOffsetForIndex(initialScrollIndex);
1340
1394
  scrollState = updatedOffset;
1341
1395
  }
1342
- let scroll = scrollState - previousScrollAdjust - topPad;
1396
+ let scroll = scrollState + scrollExtra - previousScrollAdjust - topPad;
1343
1397
  if (scroll + scrollLength > totalSize) {
1344
1398
  scroll = totalSize - scrollLength;
1345
1399
  }
@@ -1349,13 +1403,13 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1349
1403
  }
1350
1404
  let scrollBufferTop = scrollBuffer;
1351
1405
  let scrollBufferBottom = scrollBuffer;
1352
- if (scrollExtra > 8) {
1353
- scrollBufferTop = 0;
1354
- scrollBufferBottom = scrollBuffer + scrollExtra;
1406
+ if (speed > 0) {
1407
+ scrollBufferTop = scrollBuffer * 0.1;
1408
+ scrollBufferBottom = scrollBuffer * 1.9;
1355
1409
  }
1356
- if (scrollExtra < -8) {
1357
- scrollBufferTop = scrollBuffer - scrollExtra;
1358
- scrollBufferBottom = 0;
1410
+ if (speed < 0) {
1411
+ scrollBufferTop = scrollBuffer * 1.9;
1412
+ scrollBufferBottom = scrollBuffer * 0.1;
1359
1413
  }
1360
1414
  if (state.scrollForNextCalculateItemsInView) {
1361
1415
  const { top: top2, bottom } = state.scrollForNextCalculateItemsInView;
@@ -1364,7 +1418,6 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1364
1418
  }
1365
1419
  }
1366
1420
  const scrollBottom = scroll + scrollLength;
1367
- const prevEndBuffered = state.endBuffered;
1368
1421
  let startNoBuffer = null;
1369
1422
  let startBuffered = null;
1370
1423
  let startBufferedId = null;
@@ -1416,7 +1469,17 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1416
1469
  return topOffset;
1417
1470
  };
1418
1471
  let foundEnd = false;
1419
- for (let i = Math.max(0, loopStart); i < data.length && (!foundEnd || i <= prevEndBuffered); i++) {
1472
+ let lastSize;
1473
+ const prevNumContainers = ctx.values.get("numContainers");
1474
+ let maxIndexRendered = 0;
1475
+ for (let i = 0; i < prevNumContainers; i++) {
1476
+ const key = peek$(ctx, `containerItemKey${i}`);
1477
+ if (key !== void 0) {
1478
+ const index = state.indexByKey.get(key);
1479
+ maxIndexRendered = Math.max(maxIndexRendered, index);
1480
+ }
1481
+ }
1482
+ for (let i = Math.max(0, loopStart); i < data.length && (!foundEnd || i <= maxIndexRendered); i++) {
1420
1483
  const id = getId(i);
1421
1484
  const size = getItemSize(id, i, data[i], useAverageSize);
1422
1485
  maxSizeInRow = Math.max(maxSizeInRow, size);
@@ -1443,6 +1506,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1443
1506
  }
1444
1507
  if (top <= scrollBottom + scrollBufferBottom) {
1445
1508
  endBuffered = i;
1509
+ lastSize = maxSizeInRow;
1446
1510
  } else {
1447
1511
  foundEnd = true;
1448
1512
  }
@@ -1455,6 +1519,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1455
1519
  maxSizeInRow = 0;
1456
1520
  }
1457
1521
  }
1522
+ const prevStartBuffered = state.startBuffered;
1523
+ const prevEndBuffered = state.endBuffered;
1458
1524
  Object.assign(state, {
1459
1525
  startBuffered,
1460
1526
  startBufferedId,
@@ -1462,83 +1528,64 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1462
1528
  endBuffered,
1463
1529
  endNoBuffer
1464
1530
  });
1465
- const nextTop = Math.ceil(startBuffered !== null ? positions.get(startBufferedId) + scrollBuffer : 0);
1466
- const nextBottom = Math.floor(
1467
- endBuffered !== null ? (positions.get(getId(endBuffered + 1)) || 0) - scrollLength - scrollBuffer : 0
1468
- );
1469
- if (state.enableScrollForNextCalculateItemsInView) {
1531
+ if (state.enableScrollForNextCalculateItemsInView && lastSize) {
1532
+ const aboveFirst = startBuffered - 1;
1533
+ let nextTop = 0;
1534
+ if (aboveFirst >= 0) {
1535
+ const aboveFirstSize = getItemSize(getId(aboveFirst), aboveFirst, data[aboveFirst], useAverageSize);
1536
+ nextTop = scroll - aboveFirstSize;
1537
+ }
1538
+ const nextBottom = scroll + lastSize;
1470
1539
  state.scrollForNextCalculateItemsInView = nextTop >= 0 && nextBottom >= 0 ? {
1471
1540
  top: nextTop,
1472
1541
  bottom: nextBottom
1473
1542
  } : void 0;
1474
1543
  }
1475
1544
  if (startBuffered !== null && endBuffered !== null) {
1476
- const prevNumContainers = ctx.values.get("numContainers");
1477
1545
  let numContainers = prevNumContainers;
1478
- let didWarnMoreContainers = false;
1479
- const allocatedContainers = /* @__PURE__ */ new Set();
1480
- for (let i = startBuffered; i <= endBuffered; i++) {
1481
- let isContained = false;
1482
- const id = getId(i);
1483
- for (let j = 0; j < numContainers; j++) {
1484
- const key = peek$(ctx, `containerItemKey${j}`);
1485
- if (key === id) {
1486
- isContained = true;
1487
- break;
1488
- }
1489
- }
1490
- if (!isContained) {
1491
- const top2 = positions.get(id) || 0;
1492
- let furthestIndex = -1;
1493
- let furthestDistance = 0;
1494
- for (let u = 0; u < numContainers; u++) {
1495
- if (allocatedContainers.has(u)) {
1496
- continue;
1497
- }
1498
- const key = peek$(ctx, `containerItemKey${u}`);
1499
- if (key === void 0) {
1500
- furthestIndex = u;
1501
- break;
1502
- }
1503
- const index2 = state.indexByKey.get(key);
1504
- const pos = peek$(ctx, `containerPosition${u}`).top;
1505
- if (isNullOrUndefined(index2) || pos === POSITION_OUT_OF_VIEW) {
1506
- furthestIndex = u;
1507
- break;
1508
- }
1509
- if (index2 < startBuffered || index2 > endBuffered) {
1510
- const distance = Math.abs(pos - top2);
1511
- if (index2 < 0 || pos === POSITION_OUT_OF_VIEW || distance > furthestDistance) {
1512
- furthestDistance = distance;
1513
- furthestIndex = u;
1514
- }
1546
+ const needNewContainers = [];
1547
+ if (startBuffered < prevStartBuffered || endBuffered > prevEndBuffered) {
1548
+ const isContained = (i) => {
1549
+ const id = getId(i);
1550
+ for (let j = 0; j < numContainers; j++) {
1551
+ const key = peek$(ctx, `containerItemKey${j}`);
1552
+ if (key === id) {
1553
+ return true;
1515
1554
  }
1516
1555
  }
1517
- const containerId = furthestIndex >= 0 ? furthestIndex : numContainers;
1518
- set$(ctx, `containerItemKey${containerId}`, id);
1519
- const index = state.indexByKey.get(id);
1520
- set$(ctx, `containerItemData${containerId}`, data[index]);
1521
- allocatedContainers.add(containerId);
1522
- if (furthestIndex === -1) {
1523
- numContainers++;
1524
- set$(ctx, `containerItemKey${containerId}`, id);
1525
- const index2 = state.indexByKey.get(id);
1526
- set$(ctx, `containerItemData${containerId}`, data[index2]);
1527
- set$(ctx, `containerPosition${containerId}`, ANCHORED_POSITION_OUT_OF_VIEW);
1528
- set$(ctx, `containerColumn${containerId}`, -1);
1529
- if (__DEV__ && !didWarnMoreContainers && numContainers > peek$(ctx, "numContainersPooled")) {
1530
- didWarnMoreContainers = true;
1531
- console.warn(
1532
- "[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."
1533
- );
1534
- }
1556
+ };
1557
+ for (let i = startBuffered; i < prevStartBuffered; i++) {
1558
+ if (!isContained(i)) {
1559
+ needNewContainers.push(i);
1560
+ }
1561
+ }
1562
+ for (let i = Math.max(prevEndBuffered + 1, startBuffered); i <= endBuffered; i++) {
1563
+ if (!isContained(i)) {
1564
+ needNewContainers.push(i);
1535
1565
  }
1536
1566
  }
1537
1567
  }
1538
- if (numContainers !== prevNumContainers) {
1539
- set$(ctx, "numContainers", numContainers);
1540
- if (numContainers > peek$(ctx, "numContainersPooled")) {
1541
- set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
1568
+ if (needNewContainers.length > 0) {
1569
+ const availableContainers = findAvailableContainers(
1570
+ needNewContainers.length,
1571
+ startBuffered,
1572
+ endBuffered
1573
+ );
1574
+ for (let idx = 0; idx < needNewContainers.length; idx++) {
1575
+ const i = needNewContainers[idx];
1576
+ const containerIndex = availableContainers[idx];
1577
+ const id = getId(i);
1578
+ set$(ctx, `containerItemKey${containerIndex}`, id);
1579
+ set$(ctx, `containerItemData${containerIndex}`, data[i]);
1580
+ if (containerIndex >= numContainers) {
1581
+ numContainers = containerIndex + 1;
1582
+ }
1583
+ }
1584
+ if (numContainers !== prevNumContainers) {
1585
+ set$(ctx, "numContainers", numContainers);
1586
+ if (numContainers > peek$(ctx, "numContainersPooled")) {
1587
+ set$(ctx, "numContainersPooled", Math.ceil(numContainers * 1.5));
1588
+ }
1542
1589
  }
1543
1590
  }
1544
1591
  for (let i = 0; i < numContainers; i++) {
@@ -1568,7 +1615,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1568
1615
  const prevPos = peek$(ctx, `containerPosition${i}`);
1569
1616
  const prevColumn = peek$(ctx, `containerColumn${i}`);
1570
1617
  const prevData = peek$(ctx, `containerItemData${i}`);
1571
- if (pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1618
+ if (!prevPos || pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1572
1619
  set$(ctx, `containerPosition${i}`, pos);
1573
1620
  }
1574
1621
  if (column2 >= 0 && column2 !== prevColumn) {
@@ -1860,6 +1907,56 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1860
1907
  });
1861
1908
  addTotalSize(null, totalSize, totalSizeBelowIndex);
1862
1909
  };
1910
+ const findAvailableContainers = (numNeeded, startBuffered, endBuffered) => {
1911
+ const state = refState.current;
1912
+ const numContainers = peek$(ctx, "numContainers");
1913
+ if (numNeeded === 0) return [];
1914
+ const result = [];
1915
+ const availableContainers = [];
1916
+ for (let u = 0; u < numContainers; u++) {
1917
+ const key = peek$(ctx, `containerItemKey${u}`);
1918
+ if (key === void 0) {
1919
+ result.push(u);
1920
+ if (result.length >= numNeeded) {
1921
+ return result;
1922
+ }
1923
+ }
1924
+ }
1925
+ for (let u = 0; u < numContainers; u++) {
1926
+ const key = peek$(ctx, `containerItemKey${u}`);
1927
+ if (key === void 0) continue;
1928
+ const index = state.indexByKey.get(key);
1929
+ if (index < startBuffered) {
1930
+ availableContainers.push({ index: u, distance: startBuffered - index });
1931
+ } else if (index > endBuffered) {
1932
+ availableContainers.push({ index: u, distance: index - endBuffered });
1933
+ }
1934
+ }
1935
+ const remaining = numNeeded - result.length;
1936
+ if (remaining > 0) {
1937
+ if (availableContainers.length > 0) {
1938
+ if (availableContainers.length > remaining) {
1939
+ availableContainers.sort(comparatorByDistance);
1940
+ availableContainers.length = remaining;
1941
+ }
1942
+ for (const container of availableContainers) {
1943
+ result.push(container.index);
1944
+ }
1945
+ }
1946
+ const stillNeeded = numNeeded - result.length;
1947
+ if (stillNeeded > 0) {
1948
+ for (let i = 0; i < stillNeeded; i++) {
1949
+ result.push(numContainers + i);
1950
+ }
1951
+ if (__DEV__ && numContainers + stillNeeded > peek$(ctx, "numContainersPooled")) {
1952
+ console.warn(
1953
+ "[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
+ );
1955
+ }
1956
+ }
1957
+ }
1958
+ return result.sort(comparatorDefault);
1959
+ };
1863
1960
  const isFirst = !refState.current.renderItem;
1864
1961
  const memoizedLastItemKeys = useMemo(() => {
1865
1962
  if (!dataProp.length) return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@legendapp/list",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
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,