@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.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;
@@ -1436,6 +1490,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1436
1490
  return topOffset;
1437
1491
  };
1438
1492
  let foundEnd = false;
1493
+ let lastSize;
1439
1494
  const prevNumContainers = ctx.values.get("numContainers");
1440
1495
  let maxIndexRendered = 0;
1441
1496
  for (let i = 0; i < prevNumContainers; i++) {
@@ -1472,6 +1527,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1472
1527
  }
1473
1528
  if (top <= scrollBottom + scrollBufferBottom) {
1474
1529
  endBuffered = i;
1530
+ lastSize = maxSizeInRow;
1475
1531
  } else {
1476
1532
  foundEnd = true;
1477
1533
  }
@@ -1484,6 +1540,8 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1484
1540
  maxSizeInRow = 0;
1485
1541
  }
1486
1542
  }
1543
+ const prevStartBuffered = state.startBuffered;
1544
+ const prevEndBuffered = state.endBuffered;
1487
1545
  Object.assign(state, {
1488
1546
  startBuffered,
1489
1547
  startBufferedId,
@@ -1491,11 +1549,14 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1491
1549
  endBuffered,
1492
1550
  endNoBuffer
1493
1551
  });
1494
- const nextTop = Math.ceil(startBuffered !== null ? positions.get(startBufferedId) + scrollBuffer : 0);
1495
- const nextBottom = Math.floor(
1496
- endBuffered !== null ? (positions.get(getId(endBuffered + 1)) || 0) - scrollLength - scrollBuffer : 0
1497
- );
1498
- 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;
1499
1560
  state.scrollForNextCalculateItemsInView = nextTop >= 0 && nextBottom >= 0 ? {
1500
1561
  top: nextTop,
1501
1562
  bottom: nextBottom
@@ -1503,70 +1564,49 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1503
1564
  }
1504
1565
  if (startBuffered !== null && endBuffered !== null) {
1505
1566
  let numContainers = prevNumContainers;
1506
- let didWarnMoreContainers = false;
1507
- const allocatedContainers = /* @__PURE__ */ new Set();
1508
- for (let i = startBuffered; i <= endBuffered; i++) {
1509
- let isContained = false;
1510
- const id = getId(i);
1511
- for (let j = 0; j < numContainers; j++) {
1512
- const key = peek$(ctx, `containerItemKey${j}`);
1513
- if (key === id) {
1514
- isContained = true;
1515
- break;
1516
- }
1517
- }
1518
- if (!isContained) {
1519
- const top2 = positions.get(id) || 0;
1520
- let furthestIndex = -1;
1521
- let furthestDistance = 0;
1522
- for (let u = 0; u < numContainers; u++) {
1523
- if (allocatedContainers.has(u)) {
1524
- continue;
1525
- }
1526
- const key = peek$(ctx, `containerItemKey${u}`);
1527
- if (key === void 0) {
1528
- furthestIndex = u;
1529
- break;
1530
- }
1531
- const index2 = state.indexByKey.get(key);
1532
- const pos = peek$(ctx, `containerPosition${u}`).top;
1533
- if (isNullOrUndefined(index2) || pos === POSITION_OUT_OF_VIEW) {
1534
- furthestIndex = u;
1535
- break;
1536
- }
1537
- if (index2 < startBuffered || index2 > endBuffered) {
1538
- const distance = Math.abs(pos - top2);
1539
- if (index2 < 0 || pos === POSITION_OUT_OF_VIEW || distance > furthestDistance) {
1540
- furthestDistance = distance;
1541
- furthestIndex = u;
1542
- }
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;
1543
1575
  }
1544
1576
  }
1545
- const containerId = furthestIndex >= 0 ? furthestIndex : numContainers;
1546
- set$(ctx, `containerItemKey${containerId}`, id);
1547
- const index = state.indexByKey.get(id);
1548
- set$(ctx, `containerItemData${containerId}`, data[index]);
1549
- allocatedContainers.add(containerId);
1550
- if (furthestIndex === -1) {
1551
- numContainers++;
1552
- set$(ctx, `containerItemKey${containerId}`, id);
1553
- const index2 = state.indexByKey.get(id);
1554
- set$(ctx, `containerItemData${containerId}`, data[index2]);
1555
- set$(ctx, `containerPosition${containerId}`, ANCHORED_POSITION_OUT_OF_VIEW);
1556
- set$(ctx, `containerColumn${containerId}`, -1);
1557
- if (__DEV__ && !didWarnMoreContainers && numContainers > peek$(ctx, "numContainersPooled")) {
1558
- didWarnMoreContainers = true;
1559
- console.warn(
1560
- "[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."
1561
- );
1562
- }
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);
1563
1586
  }
1564
1587
  }
1565
1588
  }
1566
- if (numContainers !== prevNumContainers) {
1567
- set$(ctx, "numContainers", numContainers);
1568
- if (numContainers > peek$(ctx, "numContainersPooled")) {
1569
- 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
+ }
1570
1610
  }
1571
1611
  }
1572
1612
  for (let i = 0; i < numContainers; i++) {
@@ -1596,7 +1636,7 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1596
1636
  const prevPos = peek$(ctx, `containerPosition${i}`);
1597
1637
  const prevColumn = peek$(ctx, `containerColumn${i}`);
1598
1638
  const prevData = peek$(ctx, `containerItemData${i}`);
1599
- if (pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1639
+ if (!prevPos || pos.relativeCoordinate > POSITION_OUT_OF_VIEW && pos.top !== prevPos.top) {
1600
1640
  set$(ctx, `containerPosition${i}`, pos);
1601
1641
  }
1602
1642
  if (column2 >= 0 && column2 !== prevColumn) {
@@ -1888,6 +1928,56 @@ var LegendListInner = typedForwardRef(function LegendListInner2(props, forwarded
1888
1928
  });
1889
1929
  addTotalSize(null, totalSize, totalSizeBelowIndex);
1890
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
+ };
1891
1981
  const isFirst = !refState.current.renderItem;
1892
1982
  const memoizedLastItemKeys = React2.useMemo(() => {
1893
1983
  if (!dataProp.length) return [];