@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.
@@ -26867,10 +26867,11 @@ const createRangeReader = (
26867
26867
  });
26868
26868
  return { byIndex, count: composition.count };
26869
26869
  };
26870
- // `replace` is a revalidation: the ranks that are not in what just came back
26871
- // stood for a composition that has moved on. Otherwise the ranks are merged,
26872
- // so two lists reading the same collection through their own windows add up
26873
- // to one composition instead of taking turns erasing each other.
26870
+ // `replace` says the list dropped ranks it held (an item the collection moved
26871
+ // elsewhere, ranks past its end): kept here, they would come back at the next
26872
+ // mount. Otherwise the ranks are merged, so two lists reading the same
26873
+ // collection through their own windows add up to one composition instead of
26874
+ // taking turns erasing each other.
26874
26875
  readRange.writeComposition = ({ byIndex, count, replace }) => {
26875
26876
  const params = currentParams();
26876
26877
  let composition = findComposition(params);
@@ -77673,19 +77674,57 @@ const useRunItems = (store, {
77673
77674
  // rebuilt as the window slides — a group holds the items of its day that are
77674
77675
  // currently drawn, which is exactly the span its label has to survive.
77675
77676
  let group = null;
77677
+ // Two groups can still share a key (a failure band standing mid-group, data
77678
+ // not sorted by group): the later ones are told apart by their rank among
77679
+ // them, so the first keeps its element. A group keyed by where it starts
77680
+ // would lose its element each time the window slides past its first item.
77681
+ const groupCountByKey = new Map();
77676
77682
  const closeGroup = () => {
77677
77683
  if (!group) {
77678
77684
  return;
77679
77685
  }
77686
+ const groupCount = (groupCountByKey.get(group.key) || 0) + 1;
77687
+ groupCountByKey.set(group.key, groupCount);
77680
77688
  nodes.push(jsx(ListItemGroup, {
77681
77689
  label: group.label,
77682
77690
  labelProps: group.labelProps,
77683
77691
  children: group.children
77684
- }, `${ownerId}_group_${group.key}`));
77692
+ }, groupCount === 1 ? `${ownerId}_group_${group.key}` : `${ownerId}_group_${group.key}_${groupCount}`));
77685
77693
  group = null;
77686
77694
  };
77695
+ // An item not held has no group of its own. Between two items of the open
77696
+ // group it is a line of that group whose content has not arrived: drawn
77697
+ // outside, it would cut the group in two and show its label twice. A hole at
77698
+ // a group's edge stays outside — nothing says which side it belongs to.
77699
+ let holeTo = -1;
77700
+ let holeGroupKey;
77701
+ const groupKeyOfHole = itemIndex => {
77702
+ if (itemIndex < holeTo) {
77703
+ return holeGroupKey;
77704
+ }
77705
+ holeTo = itemIndex + 1;
77706
+ while (holeTo < windowTo && holeTo !== failureFrom && getItemAt(holeTo) === undefined) {
77707
+ holeTo++;
77708
+ }
77709
+ holeGroupKey = undefined;
77710
+ if (group && holeTo < windowTo && holeTo !== failureFrom) {
77711
+ const keyAfterHole = groupBy(getItemAt(holeTo), holeTo);
77712
+ if (keyAfterHole === group.key) {
77713
+ holeGroupKey = keyAfterHole;
77714
+ }
77715
+ }
77716
+ return holeGroupKey;
77717
+ };
77687
77718
  // Which group an item belongs to, or undefined when it belongs to none.
77688
- const groupKeyOf = (item, itemIndex) => groupBy && item !== undefined ? groupBy(item, itemIndex) : undefined;
77719
+ const groupKeyOf = (item, itemIndex) => {
77720
+ if (!groupBy) {
77721
+ return undefined;
77722
+ }
77723
+ if (item === undefined) {
77724
+ return groupKeyOfHole(itemIndex);
77725
+ }
77726
+ return groupBy(item, itemIndex);
77727
+ };
77689
77728
  const pushItem = (itemNode, item, itemIndex, groupKey) => {
77690
77729
  if (groupKey === undefined) {
77691
77730
  closeGroup();
@@ -78022,10 +78061,15 @@ const useItemStore = ({
78022
78061
  }
78023
78062
  const pages = pagesRef.current;
78024
78063
  const [, setPageVersion] = useState(0);
78025
- // The items held are out of date and the run has not asked for the new ones
78026
- // yet. They stay on screen until the answer comes: what is drawn is from
78027
- // before, which is not the same thing as nothing to draw.
78028
- const staleRef = useRef(restored);
78064
+ // The ranks whose item is from before: the run came back to the screen, or
78065
+ // the source said the collection moved. They stay drawn until a page confirms
78066
+ // or replaces them, rank by rank. A page covers what the window framed when it
78067
+ // was asked for, and the window keeps growing meanwhile: dropping what the
78068
+ // page did not cover empties items on screen.
78069
+ const staleRanksRef = useRef(null);
78070
+ if (staleRanksRef.current === null) {
78071
+ staleRanksRef.current = restored ? new Set(pages.byIndex.keys()) : new Set();
78072
+ }
78029
78073
  const [refreshing, setRefreshing] = useState(false);
78030
78074
  // A source that says when what it reads has moved (a resource range reader
78031
78075
  // does: see rerunOn.GET_RANGE) is heard here — a write deciding who belongs
@@ -78036,7 +78080,7 @@ const useItemStore = ({
78036
78080
  const invalidationRef = useRef(invalidation);
78037
78081
  if (invalidationRef.current !== invalidation) {
78038
78082
  invalidationRef.current = invalidation;
78039
- staleRef.current = true;
78083
+ staleRanksRef.current = new Set(pages.byIndex.keys());
78040
78084
  }
78041
78085
  // The one request in flight, with the means to call it off: a page asked for
78042
78086
  // a window the list has left is work the server and the browser are doing for
@@ -78069,11 +78113,6 @@ const useItemStore = ({
78069
78113
  // It stands for a page of them: a list that is about to be filled looks
78070
78114
  // like items on their way, not like an empty list.
78071
78115
  const itemCount = pages.count ?? count ?? listItems.pageSize;
78072
- // A run that never received anything has nothing to keep on screen: asking
78073
- // again is its first ask, not a refresh.
78074
- if (staleRef.current && pages.count === undefined) {
78075
- staleRef.current = false;
78076
- }
78077
78116
  useLayoutEffect(() => {
78078
78117
  if (!refreshing) {
78079
78118
  return null;
@@ -78146,9 +78185,11 @@ const useItemStore = ({
78146
78185
  const keepFrom = windowFrom - ITEM_STORE_KEEP_AROUND;
78147
78186
  const keepTo = windowTo + ITEM_STORE_KEEP_AROUND;
78148
78187
  if (pages.byIndex.size > budget) {
78188
+ const staleRanks = staleRanksRef.current;
78149
78189
  for (const index of pages.byIndex.keys()) {
78150
78190
  if (index < keepFrom || index > keepTo) {
78151
78191
  pages.byIndex.delete(index);
78192
+ staleRanks.delete(index);
78152
78193
  }
78153
78194
  }
78154
78195
  }
@@ -78183,10 +78224,24 @@ const useItemStore = ({
78183
78224
  let start = missingStart;
78184
78225
  let end = missingEnd;
78185
78226
  let around;
78186
- // Items that are all there but out of date: the ask is the window itself,
78187
- // anchored on the item at its top — a source paginating by cursor gets an
78188
- // item to count from, and the reading position is what must survive.
78189
- const revalidating = staleRef.current;
78227
+ // The window draws items from before: they are asked for again, and stay
78228
+ // drawn meanwhile.
78229
+ const staleRanks = staleRanksRef.current;
78230
+ let staleFrom = -1;
78231
+ let staleTo = -1;
78232
+ if (staleRanks.size > 0) {
78233
+ let rank = windowFrom;
78234
+ while (rank < windowTo) {
78235
+ if (staleRanks.has(rank)) {
78236
+ if (staleFrom === -1) {
78237
+ staleFrom = rank;
78238
+ }
78239
+ staleTo = rank;
78240
+ }
78241
+ rank++;
78242
+ }
78243
+ }
78244
+ const revalidating = staleFrom !== -1;
78190
78245
  // The list is held on an item nothing on screen leads to: the items it holds
78191
78246
  // do not contain it, so no window it could draw will ever bring it. Only
78192
78247
  // asking for it by name does.
@@ -78203,18 +78258,36 @@ const useItemStore = ({
78203
78258
  start = from < 0 ? 0 : from;
78204
78259
  end = start + budget - 1;
78205
78260
  } else if (revalidating) {
78206
- start = windowFrom;
78207
- end = windowTo - 1;
78208
- if (end < start) {
78209
- // Nothing of this run is on screen (the window frames another one, or
78210
- // the list is scrolled past it): its own first items are what it will
78211
- // draw next.
78212
- start = 0;
78213
- end = budget - 1;
78214
- }
78215
- const firstHeld = pages.byIndex.get(windowFrom);
78216
- if (firstHeld && firstHeld.id !== undefined) {
78217
- around = firstHeld.id;
78261
+ // The items from before that the window draws, and the missing ones
78262
+ // with them: one ask.
78263
+ if (start === -1 || staleFrom < start) {
78264
+ start = staleFrom;
78265
+ }
78266
+ if (staleTo > end) {
78267
+ end = staleTo;
78268
+ }
78269
+ // Grown over the items from before on either side, up to a page: the
78270
+ // window grows past its first picture right after it is painted, and
78271
+ // what it grows onto would otherwise be asked for once more.
78272
+ while (end - start + 1 < budget) {
78273
+ const staleBefore = staleRanks.has(start - 1);
78274
+ const staleAfter = staleRanks.has(end + 1);
78275
+ if (!staleBefore && !staleAfter) {
78276
+ break;
78277
+ }
78278
+ if (staleBefore) {
78279
+ start--;
78280
+ }
78281
+ if (staleAfter && end - start + 1 < budget) {
78282
+ end++;
78283
+ }
78284
+ }
78285
+ // Anchored on the first item from before on screen: a source
78286
+ // paginating by cursor gets an item to count from, and the reading
78287
+ // position is what must survive.
78288
+ const firstStale = pages.byIndex.get(staleFrom);
78289
+ if (firstStale && firstStale.id !== undefined) {
78290
+ around = firstStale.id;
78218
78291
  }
78219
78292
  } else if (pages.count === undefined) {
78220
78293
  const scrolled = listItems.scrolled;
@@ -78268,7 +78341,8 @@ const useItemStore = ({
78268
78341
  debugAsk("already revalidating");
78269
78342
  return;
78270
78343
  }
78271
- // A page for a window that is about to be replaced wholesale.
78344
+ // A page asked for before the items went stale answers for the
78345
+ // collection as it was.
78272
78346
  request.controller?.abort();
78273
78347
  request.busy = false;
78274
78348
  }
@@ -78289,11 +78363,11 @@ const useItemStore = ({
78289
78363
  request.controller?.abort();
78290
78364
  request.busy = false;
78291
78365
  }
78292
- const held = pages.byIndex.size;
78293
78366
  // Asking again for a range that was already asked for, having received
78294
- // nothing since, can only produce the same answer. A revalidation is
78295
- // exactly the case where it produces another one.
78296
- if (!revalidating && request.start === start && request.end === end && request.held === held) {
78367
+ // nothing since, can only produce the same answer — unless the
78368
+ // collection moved since, which is what an invalidation says.
78369
+ const held = `${pages.byIndex.size}/${staleRanks.size}/${invalidation}`;
78370
+ if (request.start === start && request.end === end && request.held === held) {
78297
78371
  debugAsk("this range was asked for already");
78298
78372
  return;
78299
78373
  }
@@ -78329,7 +78403,6 @@ const useItemStore = ({
78329
78403
  request.revalidating = false;
78330
78404
  setFailure(null);
78331
78405
  if (revalidating) {
78332
- staleRef.current = false;
78333
78406
  setRefreshing(false);
78334
78407
  }
78335
78408
  publishRequestState();
@@ -78348,15 +78421,35 @@ const useItemStore = ({
78348
78421
  // Before the items land: what is on screen has to stay where it is,
78349
78422
  // and the DOM still shows the state to hold onto.
78350
78423
  listItems.captureAnchor();
78351
- if (revalidating) {
78352
- // The items held stood for a composition that has moved on; the
78353
- // ones outside the window are forgotten and asked for again if the
78354
- // user goes back to them.
78355
- pages.byIndex = new Map();
78424
+ // An item from before is gone from where it stood when the page puts
78425
+ // it at another rank (it would be drawn twice), or when its rank is
78426
+ // past the end of the collection.
78427
+ const staleRanksNow = staleRanksRef.current;
78428
+ const pageEnd = pageStart + pageItems.length;
78429
+ let dropped = false;
78430
+ if (staleRanksNow.size > 0) {
78431
+ const pageIds = new Set();
78432
+ for (const pageItem of pageItems) {
78433
+ if (pageItem && pageItem.id !== undefined) {
78434
+ pageIds.add(pageItem.id);
78435
+ }
78436
+ }
78437
+ for (const rank of staleRanksNow) {
78438
+ if (rank >= pageStart && rank < pageEnd) {
78439
+ continue;
78440
+ }
78441
+ const staleItem = pages.byIndex.get(rank);
78442
+ if (rank >= pageCount || staleItem && pageIds.has(staleItem.id)) {
78443
+ staleRanksNow.delete(rank);
78444
+ pages.byIndex.delete(rank);
78445
+ dropped = true;
78446
+ }
78447
+ }
78356
78448
  }
78357
78449
  let i = 0;
78358
78450
  while (i < pageItems.length) {
78359
78451
  pages.byIndex.set(pageStart + i, pageItems[i]);
78452
+ staleRanksNow.delete(pageStart + i);
78360
78453
  i++;
78361
78454
  }
78362
78455
  pages.count = pageCount;
@@ -78366,7 +78459,7 @@ const useItemStore = ({
78366
78459
  itemsAction.writeComposition({
78367
78460
  byIndex: pages.byIndex,
78368
78461
  count: pageCount,
78369
- replace: revalidating
78462
+ replace: dropped
78370
78463
  });
78371
78464
  }
78372
78465
  listItems.pagesSignal.value = listItems.pagesSignal.peek() + 1;
@@ -78381,9 +78474,14 @@ const useItemStore = ({
78381
78474
  request.revalidating = false;
78382
78475
  publishRequestState();
78383
78476
  if (revalidating) {
78384
- // The items from before stay: a revalidation that failed has
78477
+ // The items from before stay, and are not asked for again until the
78478
+ // source says the collection moved: a revalidation that failed has
78385
78479
  // nothing better to put in their place.
78386
- staleRef.current = false;
78480
+ let rank = start;
78481
+ while (rank <= end) {
78482
+ staleRanksRef.current.delete(rank);
78483
+ rank++;
78484
+ }
78387
78485
  setRefreshing(false);
78388
78486
  return;
78389
78487
  }