@jsenv/navi 0.29.427 → 0.29.428

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.
@@ -25978,10 +25978,11 @@ const createRangeReader = (
25978
25978
  });
25979
25979
  return { byIndex, count: composition.count };
25980
25980
  };
25981
- // `replace` is a revalidation: the ranks that are not in what just came back
25982
- // stood for a composition that has moved on. Otherwise the ranks are merged,
25983
- // so two lists reading the same collection through their own windows add up
25984
- // to one composition instead of taking turns erasing each other.
25981
+ // `replace` says the list dropped ranks it held (an item the collection moved
25982
+ // elsewhere, ranks past its end): kept here, they would come back at the next
25983
+ // mount. Otherwise the ranks are merged, so two lists reading the same
25984
+ // collection through their own windows add up to one composition instead of
25985
+ // taking turns erasing each other.
25985
25986
  readRange.writeComposition = ({ byIndex, count, replace }) => {
25986
25987
  const params = currentParams();
25987
25988
  let composition = findComposition(params);
@@ -76192,19 +76193,57 @@ const useRunItems = (store, {
76192
76193
  // rebuilt as the window slides — a group holds the items of its day that are
76193
76194
  // currently drawn, which is exactly the span its label has to survive.
76194
76195
  let group = null;
76196
+ // Two groups can still share a key (a failure band standing mid-group, data
76197
+ // not sorted by group): the later ones are told apart by their rank among
76198
+ // them, so the first keeps its element. A group keyed by where it starts
76199
+ // would lose its element each time the window slides past its first item.
76200
+ const groupCountByKey = new Map();
76195
76201
  const closeGroup = () => {
76196
76202
  if (!group) {
76197
76203
  return;
76198
76204
  }
76205
+ const groupCount = (groupCountByKey.get(group.key) || 0) + 1;
76206
+ groupCountByKey.set(group.key, groupCount);
76199
76207
  nodes.push(jsx(ListItemGroup, {
76200
76208
  label: group.label,
76201
76209
  labelProps: group.labelProps,
76202
76210
  children: group.children
76203
- }, `${ownerId}_group_${group.key}`));
76211
+ }, groupCount === 1 ? `${ownerId}_group_${group.key}` : `${ownerId}_group_${group.key}_${groupCount}`));
76204
76212
  group = null;
76205
76213
  };
76214
+ // An item not held has no group of its own. Between two items of the open
76215
+ // group it is a line of that group whose content has not arrived: drawn
76216
+ // outside, it would cut the group in two and show its label twice. A hole at
76217
+ // a group's edge stays outside — nothing says which side it belongs to.
76218
+ let holeTo = -1;
76219
+ let holeGroupKey;
76220
+ const groupKeyOfHole = itemIndex => {
76221
+ if (itemIndex < holeTo) {
76222
+ return holeGroupKey;
76223
+ }
76224
+ holeTo = itemIndex + 1;
76225
+ while (holeTo < windowTo && holeTo !== failureFrom && getItemAt(holeTo) === undefined) {
76226
+ holeTo++;
76227
+ }
76228
+ holeGroupKey = undefined;
76229
+ if (group && holeTo < windowTo && holeTo !== failureFrom) {
76230
+ const keyAfterHole = groupBy(getItemAt(holeTo), holeTo);
76231
+ if (keyAfterHole === group.key) {
76232
+ holeGroupKey = keyAfterHole;
76233
+ }
76234
+ }
76235
+ return holeGroupKey;
76236
+ };
76206
76237
  // Which group an item belongs to, or undefined when it belongs to none.
76207
- const groupKeyOf = (item, itemIndex) => groupBy && item !== undefined ? groupBy(item, itemIndex) : undefined;
76238
+ const groupKeyOf = (item, itemIndex) => {
76239
+ if (!groupBy) {
76240
+ return undefined;
76241
+ }
76242
+ if (item === undefined) {
76243
+ return groupKeyOfHole(itemIndex);
76244
+ }
76245
+ return groupBy(item, itemIndex);
76246
+ };
76208
76247
  const pushItem = (itemNode, item, itemIndex, groupKey) => {
76209
76248
  if (groupKey === undefined) {
76210
76249
  closeGroup();
@@ -76532,10 +76571,15 @@ const useItemStore = ({
76532
76571
  }
76533
76572
  const pages = pagesRef.current;
76534
76573
  const [, setPageVersion] = useState(0);
76535
- // The items held are out of date and the run has not asked for the new ones
76536
- // yet. They stay on screen until the answer comes: what is drawn is from
76537
- // before, which is not the same thing as nothing to draw.
76538
- const staleRef = useRef(restored);
76574
+ // The ranks whose item is from before: the run came back to the screen, or
76575
+ // the source said the collection moved. They stay drawn until a page confirms
76576
+ // or replaces them, rank by rank. A page covers what the window framed when it
76577
+ // was asked for, and the window keeps growing meanwhile: dropping what the
76578
+ // page did not cover empties items on screen.
76579
+ const staleRanksRef = useRef(null);
76580
+ if (staleRanksRef.current === null) {
76581
+ staleRanksRef.current = restored ? new Set(pages.byIndex.keys()) : new Set();
76582
+ }
76539
76583
  const [refreshing, setRefreshing] = useState(false);
76540
76584
  // A source that says when what it reads has moved (a resource range reader
76541
76585
  // does: see rerunOn.GET_RANGE) is heard here — a write deciding who belongs
@@ -76546,7 +76590,7 @@ const useItemStore = ({
76546
76590
  const invalidationRef = useRef(invalidation);
76547
76591
  if (invalidationRef.current !== invalidation) {
76548
76592
  invalidationRef.current = invalidation;
76549
- staleRef.current = true;
76593
+ staleRanksRef.current = new Set(pages.byIndex.keys());
76550
76594
  }
76551
76595
  // The one request in flight, with the means to call it off: a page asked for
76552
76596
  // a window the list has left is work the server and the browser are doing for
@@ -76579,11 +76623,6 @@ const useItemStore = ({
76579
76623
  // It stands for a page of them: a list that is about to be filled looks
76580
76624
  // like items on their way, not like an empty list.
76581
76625
  const itemCount = pages.count ?? count ?? listItems.pageSize;
76582
- // A run that never received anything has nothing to keep on screen: asking
76583
- // again is its first ask, not a refresh.
76584
- if (staleRef.current && pages.count === undefined) {
76585
- staleRef.current = false;
76586
- }
76587
76626
  useLayoutEffect(() => {
76588
76627
  if (!refreshing) {
76589
76628
  return null;
@@ -76656,9 +76695,11 @@ const useItemStore = ({
76656
76695
  const keepFrom = windowFrom - ITEM_STORE_KEEP_AROUND;
76657
76696
  const keepTo = windowTo + ITEM_STORE_KEEP_AROUND;
76658
76697
  if (pages.byIndex.size > budget) {
76698
+ const staleRanks = staleRanksRef.current;
76659
76699
  for (const index of pages.byIndex.keys()) {
76660
76700
  if (index < keepFrom || index > keepTo) {
76661
76701
  pages.byIndex.delete(index);
76702
+ staleRanks.delete(index);
76662
76703
  }
76663
76704
  }
76664
76705
  }
@@ -76693,10 +76734,24 @@ const useItemStore = ({
76693
76734
  let start = missingStart;
76694
76735
  let end = missingEnd;
76695
76736
  let around;
76696
- // Items that are all there but out of date: the ask is the window itself,
76697
- // anchored on the item at its top — a source paginating by cursor gets an
76698
- // item to count from, and the reading position is what must survive.
76699
- const revalidating = staleRef.current;
76737
+ // The window draws items from before: they are asked for again, and stay
76738
+ // drawn meanwhile.
76739
+ const staleRanks = staleRanksRef.current;
76740
+ let staleFrom = -1;
76741
+ let staleTo = -1;
76742
+ if (staleRanks.size > 0) {
76743
+ let rank = windowFrom;
76744
+ while (rank < windowTo) {
76745
+ if (staleRanks.has(rank)) {
76746
+ if (staleFrom === -1) {
76747
+ staleFrom = rank;
76748
+ }
76749
+ staleTo = rank;
76750
+ }
76751
+ rank++;
76752
+ }
76753
+ }
76754
+ const revalidating = staleFrom !== -1;
76700
76755
  // The list is held on an item nothing on screen leads to: the items it holds
76701
76756
  // do not contain it, so no window it could draw will ever bring it. Only
76702
76757
  // asking for it by name does.
@@ -76713,18 +76768,36 @@ const useItemStore = ({
76713
76768
  start = from < 0 ? 0 : from;
76714
76769
  end = start + budget - 1;
76715
76770
  } else if (revalidating) {
76716
- start = windowFrom;
76717
- end = windowTo - 1;
76718
- if (end < start) {
76719
- // Nothing of this run is on screen (the window frames another one, or
76720
- // the list is scrolled past it): its own first items are what it will
76721
- // draw next.
76722
- start = 0;
76723
- end = budget - 1;
76724
- }
76725
- const firstHeld = pages.byIndex.get(windowFrom);
76726
- if (firstHeld && firstHeld.id !== undefined) {
76727
- around = firstHeld.id;
76771
+ // The items from before that the window draws, and the missing ones
76772
+ // with them: one ask.
76773
+ if (start === -1 || staleFrom < start) {
76774
+ start = staleFrom;
76775
+ }
76776
+ if (staleTo > end) {
76777
+ end = staleTo;
76778
+ }
76779
+ // Grown over the items from before on either side, up to a page: the
76780
+ // window grows past its first picture right after it is painted, and
76781
+ // what it grows onto would otherwise be asked for once more.
76782
+ while (end - start + 1 < budget) {
76783
+ const staleBefore = staleRanks.has(start - 1);
76784
+ const staleAfter = staleRanks.has(end + 1);
76785
+ if (!staleBefore && !staleAfter) {
76786
+ break;
76787
+ }
76788
+ if (staleBefore) {
76789
+ start--;
76790
+ }
76791
+ if (staleAfter && end - start + 1 < budget) {
76792
+ end++;
76793
+ }
76794
+ }
76795
+ // Anchored on the first item from before on screen: a source
76796
+ // paginating by cursor gets an item to count from, and the reading
76797
+ // position is what must survive.
76798
+ const firstStale = pages.byIndex.get(staleFrom);
76799
+ if (firstStale && firstStale.id !== undefined) {
76800
+ around = firstStale.id;
76728
76801
  }
76729
76802
  } else if (pages.count === undefined) {
76730
76803
  const scrolled = listItems.scrolled;
@@ -76778,7 +76851,8 @@ const useItemStore = ({
76778
76851
  debugAsk("already revalidating");
76779
76852
  return;
76780
76853
  }
76781
- // A page for a window that is about to be replaced wholesale.
76854
+ // A page asked for before the items went stale answers for the
76855
+ // collection as it was.
76782
76856
  request.controller?.abort();
76783
76857
  request.busy = false;
76784
76858
  }
@@ -76799,11 +76873,11 @@ const useItemStore = ({
76799
76873
  request.controller?.abort();
76800
76874
  request.busy = false;
76801
76875
  }
76802
- const held = pages.byIndex.size;
76803
76876
  // Asking again for a range that was already asked for, having received
76804
- // nothing since, can only produce the same answer. A revalidation is
76805
- // exactly the case where it produces another one.
76806
- if (!revalidating && request.start === start && request.end === end && request.held === held) {
76877
+ // nothing since, can only produce the same answer — unless the
76878
+ // collection moved since, which is what an invalidation says.
76879
+ const held = `${pages.byIndex.size}/${staleRanks.size}/${invalidation}`;
76880
+ if (request.start === start && request.end === end && request.held === held) {
76807
76881
  debugAsk("this range was asked for already");
76808
76882
  return;
76809
76883
  }
@@ -76839,7 +76913,6 @@ const useItemStore = ({
76839
76913
  request.revalidating = false;
76840
76914
  setFailure(null);
76841
76915
  if (revalidating) {
76842
- staleRef.current = false;
76843
76916
  setRefreshing(false);
76844
76917
  }
76845
76918
  publishRequestState();
@@ -76858,15 +76931,35 @@ const useItemStore = ({
76858
76931
  // Before the items land: what is on screen has to stay where it is,
76859
76932
  // and the DOM still shows the state to hold onto.
76860
76933
  listItems.captureAnchor();
76861
- if (revalidating) {
76862
- // The items held stood for a composition that has moved on; the
76863
- // ones outside the window are forgotten and asked for again if the
76864
- // user goes back to them.
76865
- pages.byIndex = new Map();
76934
+ // An item from before is gone from where it stood when the page puts
76935
+ // it at another rank (it would be drawn twice), or when its rank is
76936
+ // past the end of the collection.
76937
+ const staleRanksNow = staleRanksRef.current;
76938
+ const pageEnd = pageStart + pageItems.length;
76939
+ let dropped = false;
76940
+ if (staleRanksNow.size > 0) {
76941
+ const pageIds = new Set();
76942
+ for (const pageItem of pageItems) {
76943
+ if (pageItem && pageItem.id !== undefined) {
76944
+ pageIds.add(pageItem.id);
76945
+ }
76946
+ }
76947
+ for (const rank of staleRanksNow) {
76948
+ if (rank >= pageStart && rank < pageEnd) {
76949
+ continue;
76950
+ }
76951
+ const staleItem = pages.byIndex.get(rank);
76952
+ if (rank >= pageCount || staleItem && pageIds.has(staleItem.id)) {
76953
+ staleRanksNow.delete(rank);
76954
+ pages.byIndex.delete(rank);
76955
+ dropped = true;
76956
+ }
76957
+ }
76866
76958
  }
76867
76959
  let i = 0;
76868
76960
  while (i < pageItems.length) {
76869
76961
  pages.byIndex.set(pageStart + i, pageItems[i]);
76962
+ staleRanksNow.delete(pageStart + i);
76870
76963
  i++;
76871
76964
  }
76872
76965
  pages.count = pageCount;
@@ -76876,7 +76969,7 @@ const useItemStore = ({
76876
76969
  itemsAction.writeComposition({
76877
76970
  byIndex: pages.byIndex,
76878
76971
  count: pageCount,
76879
- replace: revalidating
76972
+ replace: dropped
76880
76973
  });
76881
76974
  }
76882
76975
  listItems.pagesSignal.value = listItems.pagesSignal.peek() + 1;
@@ -76891,9 +76984,14 @@ const useItemStore = ({
76891
76984
  request.revalidating = false;
76892
76985
  publishRequestState();
76893
76986
  if (revalidating) {
76894
- // The items from before stay: a revalidation that failed has
76987
+ // The items from before stay, and are not asked for again until the
76988
+ // source says the collection moved: a revalidation that failed has
76895
76989
  // nothing better to put in their place.
76896
- staleRef.current = false;
76990
+ let rank = start;
76991
+ while (rank <= end) {
76992
+ staleRanksRef.current.delete(rank);
76993
+ rank++;
76994
+ }
76897
76995
  setRefreshing(false);
76898
76996
  return;
76899
76997
  }