@legendapp/list 1.0.7 → 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.
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;
@@ -1415,6 +1469,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1415
1469
  return topOffset;
1416
1470
  };
1417
1471
  let foundEnd = false;
1472
+ let lastSize;
1418
1473
  const prevNumContainers = ctx.values.get("numContainers");
1419
1474
  let maxIndexRendered = 0;
1420
1475
  for (let i = 0; i < prevNumContainers; i++) {
@@ -1451,6 +1506,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1451
1506
  }
1452
1507
  if (top <= scrollBottom + scrollBufferBottom) {
1453
1508
  endBuffered = i;
1509
+ lastSize = maxSizeInRow;
1454
1510
  } else {
1455
1511
  foundEnd = true;
1456
1512
  }
@@ -1463,6 +1519,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1463
1519
  maxSizeInRow = 0;
1464
1520
  }
1465
1521
  }
1522
+ const prevStartBuffered = state.startBuffered;
1523
+ const prevEndBuffered = state.endBuffered;
1466
1524
  Object.assign(state, {
1467
1525
  startBuffered,
1468
1526
  startBufferedId,
@@ -1470,11 +1528,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1470
1528
  endBuffered,
1471
1529
  endNoBuffer
1472
1530
  });
1473
- const nextTop = Math.ceil(startBuffered !== null ? positions.get(startBufferedId) + scrollBuffer : 0);
1474
- const nextBottom = Math.floor(
1475
- endBuffered !== null ? (positions.get(getId(endBuffered + 1)) || 0) - scrollLength - scrollBuffer : 0
1476
- );
1477
- 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;
1478
1539
  state.scrollForNextCalculateItemsInView = nextTop >= 0 && nextBottom >= 0 ? {
1479
1540
  top: nextTop,
1480
1541
  bottom: nextBottom
@@ -1482,70 +1543,49 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1482
1543
  }
1483
1544
  if (startBuffered !== null && endBuffered !== null) {
1484
1545
  let numContainers = prevNumContainers;
1485
- let didWarnMoreContainers = false;
1486
- const allocatedContainers = /* @__PURE__ */ new Set();
1487
- for (let i = startBuffered; i <= endBuffered; i++) {
1488
- let isContained = false;
1489
- const id = getId(i);
1490
- for (let j = 0; j < numContainers; j++) {
1491
- const key = peek$(ctx, `containerItemKey${j}`);
1492
- if (key === id) {
1493
- isContained = true;
1494
- break;
1495
- }
1496
- }
1497
- if (!isContained) {
1498
- const top2 = positions.get(id) || 0;
1499
- let furthestIndex = -1;
1500
- let furthestDistance = 0;
1501
- for (let u = 0; u < numContainers; u++) {
1502
- if (allocatedContainers.has(u)) {
1503
- continue;
1504
- }
1505
- const key = peek$(ctx, `containerItemKey${u}`);
1506
- if (key === void 0) {
1507
- furthestIndex = u;
1508
- break;
1509
- }
1510
- const index2 = state.indexByKey.get(key);
1511
- const pos = peek$(ctx, `containerPosition${u}`).top;
1512
- if (isNullOrUndefined(index2) || pos === POSITION_OUT_OF_VIEW) {
1513
- furthestIndex = u;
1514
- break;
1515
- }
1516
- if (index2 < startBuffered || index2 > endBuffered) {
1517
- const distance = Math.abs(pos - top2);
1518
- if (index2 < 0 || pos === POSITION_OUT_OF_VIEW || distance > furthestDistance) {
1519
- furthestDistance = distance;
1520
- furthestIndex = u;
1521
- }
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;
1522
1554
  }
1523
1555
  }
1524
- const containerId = furthestIndex >= 0 ? furthestIndex : numContainers;
1525
- set$(ctx, `containerItemKey${containerId}`, id);
1526
- const index = state.indexByKey.get(id);
1527
- set$(ctx, `containerItemData${containerId}`, data[index]);
1528
- allocatedContainers.add(containerId);
1529
- if (furthestIndex === -1) {
1530
- numContainers++;
1531
- set$(ctx, `containerItemKey${containerId}`, id);
1532
- const index2 = state.indexByKey.get(id);
1533
- set$(ctx, `containerItemData${containerId}`, data[index2]);
1534
- set$(ctx, `containerPosition${containerId}`, ANCHORED_POSITION_OUT_OF_VIEW);
1535
- set$(ctx, `containerColumn${containerId}`, -1);
1536
- if (__DEV__ && !didWarnMoreContainers && numContainers > peek$(ctx, "numContainersPooled")) {
1537
- didWarnMoreContainers = true;
1538
- console.warn(
1539
- "[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."
1540
- );
1541
- }
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);
1542
1565
  }
1543
1566
  }
1544
1567
  }
1545
- if (numContainers !== prevNumContainers) {
1546
- set$(ctx, "numContainers", numContainers);
1547
- if (numContainers > peek$(ctx, "numContainersPooled")) {
1548
- 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
+ }
1549
1589
  }
1550
1590
  }
1551
1591
  for (let i = 0; i < numContainers; i++) {
@@ -1575,7 +1615,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1575
1615
  const prevPos = peek$(ctx, `containerPosition${i}`);
1576
1616
  const prevColumn = peek$(ctx, `containerColumn${i}`);
1577
1617
  const prevData = peek$(ctx, `containerItemData${i}`);
1578
- if (pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1618
+ if (!prevPos || pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1579
1619
  set$(ctx, `containerPosition${i}`, pos);
1580
1620
  }
1581
1621
  if (column2 >= 0 && column2 !== prevColumn) {
@@ -1867,6 +1907,56 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1867
1907
  });
1868
1908
  addTotalSize(null, totalSize, totalSizeBelowIndex);
1869
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
+ };
1870
1960
  const isFirst = !refState.current.renderItem;
1871
1961
  const memoizedLastItemKeys = useMemo(() => {
1872
1962
  if (!dataProp.length) return [];