@jsenv/navi 0.29.427 → 0.29.429

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);
@@ -38774,6 +38775,16 @@ const resolveAfterSend = (target, requester) => {
38774
38775
  }
38775
38776
  return undefined;
38776
38777
  };
38778
+ // What follows a send is caused by the send, not by the gesture: the send
38779
+ // answered the gesture and may have prevented it (an Enter must not also click
38780
+ // the submit button, nor activate the trigger of the popup it closed), and a
38781
+ // follow-up judged on that event is refused as a gesture someone cancelled.
38782
+ // Chained, so the gesture stays readable behind it.
38783
+ const createSentEvent = (event) => {
38784
+ const sentEvent = new CustomEvent("sent", { detail: {} });
38785
+ chainEvent(sentEvent, event);
38786
+ return sentEvent;
38787
+ };
38777
38788
 
38778
38789
  registerNaviCommand("--navi-send", (source, event, { requester }) => {
38779
38790
  const expandable = resolveExpandableAround(source);
@@ -38806,7 +38817,9 @@ registerNaviCommand("--navi-send", (source, event, { requester }) => {
38806
38817
  source.getAttribute("data-after-send") ||
38807
38818
  target.getAttribute("data-after-send");
38808
38819
  if (afterSend) {
38809
- triggerNaviCommand(target, afterSend, event, { optional: true });
38820
+ triggerNaviCommand(target, afterSend, createSentEvent(event), {
38821
+ optional: true,
38822
+ });
38810
38823
  }
38811
38824
  return result;
38812
38825
  },
@@ -38850,7 +38863,9 @@ registerNaviCommand("--navi-send", (source, event, { requester }) => {
38850
38863
  if (!afterSend) {
38851
38864
  return;
38852
38865
  }
38853
- triggerNaviCommand(source, afterSend, event, { optional: true });
38866
+ triggerNaviCommand(source, afterSend, createSentEvent(event), {
38867
+ optional: true,
38868
+ });
38854
38869
  };
38855
38870
  const {
38856
38871
  result: sent,
@@ -72727,9 +72742,12 @@ const useListScrollSync = ({
72727
72742
  };
72728
72743
  const [renderWindow, setRenderWindow] = useState(() => {
72729
72744
  // Opening somewhere else than the beginning starts by framing there: the
72730
- // items the list will draw are the items it will ask for.
72745
+ // items the list will draw are the items it will ask for. A named item is
72746
+ // framed where it stood when it was written down, which is where holdWindow
72747
+ // aims before anyone can say where it is.
72731
72748
  const openAt = scrolled ?? defaultScrolled;
72732
- const start = typeof openAt === "number" ? openAt - itemsAboveOpening(openAt, firstWindowItemCount) : 0;
72749
+ const openIndex = typeof openAt === "number" ? openAt : openAt && typeof openAt === "object" && typeof openAt.index === "number" ? openAt.index : null;
72750
+ const start = openIndex === null ? 0 : openIndex - itemsAboveOpening(openAt, firstWindowItemCount);
72733
72751
  const startClamped = start < 0 ? 0 : start;
72734
72752
  return {
72735
72753
  start: startClamped,
@@ -72855,14 +72873,11 @@ const useListScrollSync = ({
72855
72873
  if (wantedStart < 0) {
72856
72874
  wantedStart = 0;
72857
72875
  }
72858
- if (wantedStart === start) {
72859
- listItems.holdPending = false;
72860
- return;
72861
- }
72862
- renderWindowRef.current = {
72863
- start: wantedStart,
72864
- end: wantedStart + windowSize
72865
- };
72876
+ // The window the hold frames goes into the state, not into this render
72877
+ // alone: the next render starts from the state, and once the item stands
72878
+ // inside the window the hold stops framing it — a window left where the
72879
+ // state had it would draw around another place.
72880
+ updateRenderWindow(wantedStart, wantedStart + windowSize);
72866
72881
  listItems.holdPending = false;
72867
72882
  };
72868
72883
  const pendingScrollRef = useRef();
@@ -73010,6 +73025,13 @@ const useListScrollSync = ({
73010
73025
  return;
73011
73026
  }
73012
73027
  hasBeenDisplayedRef.current = true;
73028
+ const place = startPlaceRef.current;
73029
+ if (!place.userTookOver && place.wanted !== "start" && place.wanted !== undefined) {
73030
+ // Held somewhere: where the list opens is the hold's to say (see
73031
+ // placeWhereHeld). Scrolled here too, the list would go to the top or
73032
+ // the selected item for as long as the hold takes to put it back.
73033
+ return;
73034
+ }
73013
73035
  const items = listItems.itemsSignal.peek();
73014
73036
  const firstSelected = items.find(i => {
73015
73037
  if (i.selected) {
@@ -76192,19 +76214,57 @@ const useRunItems = (store, {
76192
76214
  // rebuilt as the window slides — a group holds the items of its day that are
76193
76215
  // currently drawn, which is exactly the span its label has to survive.
76194
76216
  let group = null;
76217
+ // Two groups can still share a key (a failure band standing mid-group, data
76218
+ // not sorted by group): the later ones are told apart by their rank among
76219
+ // them, so the first keeps its element. A group keyed by where it starts
76220
+ // would lose its element each time the window slides past its first item.
76221
+ const groupCountByKey = new Map();
76195
76222
  const closeGroup = () => {
76196
76223
  if (!group) {
76197
76224
  return;
76198
76225
  }
76226
+ const groupCount = (groupCountByKey.get(group.key) || 0) + 1;
76227
+ groupCountByKey.set(group.key, groupCount);
76199
76228
  nodes.push(jsx(ListItemGroup, {
76200
76229
  label: group.label,
76201
76230
  labelProps: group.labelProps,
76202
76231
  children: group.children
76203
- }, `${ownerId}_group_${group.key}`));
76232
+ }, groupCount === 1 ? `${ownerId}_group_${group.key}` : `${ownerId}_group_${group.key}_${groupCount}`));
76204
76233
  group = null;
76205
76234
  };
76235
+ // An item not held has no group of its own. Between two items of the open
76236
+ // group it is a line of that group whose content has not arrived: drawn
76237
+ // outside, it would cut the group in two and show its label twice. A hole at
76238
+ // a group's edge stays outside — nothing says which side it belongs to.
76239
+ let holeTo = -1;
76240
+ let holeGroupKey;
76241
+ const groupKeyOfHole = itemIndex => {
76242
+ if (itemIndex < holeTo) {
76243
+ return holeGroupKey;
76244
+ }
76245
+ holeTo = itemIndex + 1;
76246
+ while (holeTo < windowTo && holeTo !== failureFrom && getItemAt(holeTo) === undefined) {
76247
+ holeTo++;
76248
+ }
76249
+ holeGroupKey = undefined;
76250
+ if (group && holeTo < windowTo && holeTo !== failureFrom) {
76251
+ const keyAfterHole = groupBy(getItemAt(holeTo), holeTo);
76252
+ if (keyAfterHole === group.key) {
76253
+ holeGroupKey = keyAfterHole;
76254
+ }
76255
+ }
76256
+ return holeGroupKey;
76257
+ };
76206
76258
  // 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;
76259
+ const groupKeyOf = (item, itemIndex) => {
76260
+ if (!groupBy) {
76261
+ return undefined;
76262
+ }
76263
+ if (item === undefined) {
76264
+ return groupKeyOfHole(itemIndex);
76265
+ }
76266
+ return groupBy(item, itemIndex);
76267
+ };
76208
76268
  const pushItem = (itemNode, item, itemIndex, groupKey) => {
76209
76269
  if (groupKey === undefined) {
76210
76270
  closeGroup();
@@ -76532,10 +76592,15 @@ const useItemStore = ({
76532
76592
  }
76533
76593
  const pages = pagesRef.current;
76534
76594
  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);
76595
+ // The ranks whose item is from before: the run came back to the screen, or
76596
+ // the source said the collection moved. They stay drawn until a page confirms
76597
+ // or replaces them, rank by rank. A page covers what the window framed when it
76598
+ // was asked for, and the window keeps growing meanwhile: dropping what the
76599
+ // page did not cover empties items on screen.
76600
+ const staleRanksRef = useRef(null);
76601
+ if (staleRanksRef.current === null) {
76602
+ staleRanksRef.current = restored ? new Set(pages.byIndex.keys()) : new Set();
76603
+ }
76539
76604
  const [refreshing, setRefreshing] = useState(false);
76540
76605
  // A source that says when what it reads has moved (a resource range reader
76541
76606
  // does: see rerunOn.GET_RANGE) is heard here — a write deciding who belongs
@@ -76546,7 +76611,7 @@ const useItemStore = ({
76546
76611
  const invalidationRef = useRef(invalidation);
76547
76612
  if (invalidationRef.current !== invalidation) {
76548
76613
  invalidationRef.current = invalidation;
76549
- staleRef.current = true;
76614
+ staleRanksRef.current = new Set(pages.byIndex.keys());
76550
76615
  }
76551
76616
  // The one request in flight, with the means to call it off: a page asked for
76552
76617
  // a window the list has left is work the server and the browser are doing for
@@ -76579,11 +76644,6 @@ const useItemStore = ({
76579
76644
  // It stands for a page of them: a list that is about to be filled looks
76580
76645
  // like items on their way, not like an empty list.
76581
76646
  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
76647
  useLayoutEffect(() => {
76588
76648
  if (!refreshing) {
76589
76649
  return null;
@@ -76656,9 +76716,11 @@ const useItemStore = ({
76656
76716
  const keepFrom = windowFrom - ITEM_STORE_KEEP_AROUND;
76657
76717
  const keepTo = windowTo + ITEM_STORE_KEEP_AROUND;
76658
76718
  if (pages.byIndex.size > budget) {
76719
+ const staleRanks = staleRanksRef.current;
76659
76720
  for (const index of pages.byIndex.keys()) {
76660
76721
  if (index < keepFrom || index > keepTo) {
76661
76722
  pages.byIndex.delete(index);
76723
+ staleRanks.delete(index);
76662
76724
  }
76663
76725
  }
76664
76726
  }
@@ -76693,10 +76755,24 @@ const useItemStore = ({
76693
76755
  let start = missingStart;
76694
76756
  let end = missingEnd;
76695
76757
  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;
76758
+ // The window draws items from before: they are asked for again, and stay
76759
+ // drawn meanwhile.
76760
+ const staleRanks = staleRanksRef.current;
76761
+ let staleFrom = -1;
76762
+ let staleTo = -1;
76763
+ if (staleRanks.size > 0) {
76764
+ let rank = windowFrom;
76765
+ while (rank < windowTo) {
76766
+ if (staleRanks.has(rank)) {
76767
+ if (staleFrom === -1) {
76768
+ staleFrom = rank;
76769
+ }
76770
+ staleTo = rank;
76771
+ }
76772
+ rank++;
76773
+ }
76774
+ }
76775
+ const revalidating = staleFrom !== -1;
76700
76776
  // The list is held on an item nothing on screen leads to: the items it holds
76701
76777
  // do not contain it, so no window it could draw will ever bring it. Only
76702
76778
  // asking for it by name does.
@@ -76713,18 +76789,36 @@ const useItemStore = ({
76713
76789
  start = from < 0 ? 0 : from;
76714
76790
  end = start + budget - 1;
76715
76791
  } 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;
76792
+ // The items from before that the window draws, and the missing ones
76793
+ // with them: one ask.
76794
+ if (start === -1 || staleFrom < start) {
76795
+ start = staleFrom;
76796
+ }
76797
+ if (staleTo > end) {
76798
+ end = staleTo;
76799
+ }
76800
+ // Grown over the items from before on either side, up to a page: the
76801
+ // window grows past its first picture right after it is painted, and
76802
+ // what it grows onto would otherwise be asked for once more.
76803
+ while (end - start + 1 < budget) {
76804
+ const staleBefore = staleRanks.has(start - 1);
76805
+ const staleAfter = staleRanks.has(end + 1);
76806
+ if (!staleBefore && !staleAfter) {
76807
+ break;
76808
+ }
76809
+ if (staleBefore) {
76810
+ start--;
76811
+ }
76812
+ if (staleAfter && end - start + 1 < budget) {
76813
+ end++;
76814
+ }
76815
+ }
76816
+ // Anchored on the first item from before on screen: a source
76817
+ // paginating by cursor gets an item to count from, and the reading
76818
+ // position is what must survive.
76819
+ const firstStale = pages.byIndex.get(staleFrom);
76820
+ if (firstStale && firstStale.id !== undefined) {
76821
+ around = firstStale.id;
76728
76822
  }
76729
76823
  } else if (pages.count === undefined) {
76730
76824
  const scrolled = listItems.scrolled;
@@ -76778,7 +76872,8 @@ const useItemStore = ({
76778
76872
  debugAsk("already revalidating");
76779
76873
  return;
76780
76874
  }
76781
- // A page for a window that is about to be replaced wholesale.
76875
+ // A page asked for before the items went stale answers for the
76876
+ // collection as it was.
76782
76877
  request.controller?.abort();
76783
76878
  request.busy = false;
76784
76879
  }
@@ -76799,11 +76894,11 @@ const useItemStore = ({
76799
76894
  request.controller?.abort();
76800
76895
  request.busy = false;
76801
76896
  }
76802
- const held = pages.byIndex.size;
76803
76897
  // 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) {
76898
+ // nothing since, can only produce the same answer — unless the
76899
+ // collection moved since, which is what an invalidation says.
76900
+ const held = `${pages.byIndex.size}/${staleRanks.size}/${invalidation}`;
76901
+ if (request.start === start && request.end === end && request.held === held) {
76807
76902
  debugAsk("this range was asked for already");
76808
76903
  return;
76809
76904
  }
@@ -76839,7 +76934,6 @@ const useItemStore = ({
76839
76934
  request.revalidating = false;
76840
76935
  setFailure(null);
76841
76936
  if (revalidating) {
76842
- staleRef.current = false;
76843
76937
  setRefreshing(false);
76844
76938
  }
76845
76939
  publishRequestState();
@@ -76858,15 +76952,35 @@ const useItemStore = ({
76858
76952
  // Before the items land: what is on screen has to stay where it is,
76859
76953
  // and the DOM still shows the state to hold onto.
76860
76954
  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();
76955
+ // An item from before is gone from where it stood when the page puts
76956
+ // it at another rank (it would be drawn twice), or when its rank is
76957
+ // past the end of the collection.
76958
+ const staleRanksNow = staleRanksRef.current;
76959
+ const pageEnd = pageStart + pageItems.length;
76960
+ let dropped = false;
76961
+ if (staleRanksNow.size > 0) {
76962
+ const pageIds = new Set();
76963
+ for (const pageItem of pageItems) {
76964
+ if (pageItem && pageItem.id !== undefined) {
76965
+ pageIds.add(pageItem.id);
76966
+ }
76967
+ }
76968
+ for (const rank of staleRanksNow) {
76969
+ if (rank >= pageStart && rank < pageEnd) {
76970
+ continue;
76971
+ }
76972
+ const staleItem = pages.byIndex.get(rank);
76973
+ if (rank >= pageCount || staleItem && pageIds.has(staleItem.id)) {
76974
+ staleRanksNow.delete(rank);
76975
+ pages.byIndex.delete(rank);
76976
+ dropped = true;
76977
+ }
76978
+ }
76866
76979
  }
76867
76980
  let i = 0;
76868
76981
  while (i < pageItems.length) {
76869
76982
  pages.byIndex.set(pageStart + i, pageItems[i]);
76983
+ staleRanksNow.delete(pageStart + i);
76870
76984
  i++;
76871
76985
  }
76872
76986
  pages.count = pageCount;
@@ -76876,7 +76990,7 @@ const useItemStore = ({
76876
76990
  itemsAction.writeComposition({
76877
76991
  byIndex: pages.byIndex,
76878
76992
  count: pageCount,
76879
- replace: revalidating
76993
+ replace: dropped
76880
76994
  });
76881
76995
  }
76882
76996
  listItems.pagesSignal.value = listItems.pagesSignal.peek() + 1;
@@ -76891,9 +77005,14 @@ const useItemStore = ({
76891
77005
  request.revalidating = false;
76892
77006
  publishRequestState();
76893
77007
  if (revalidating) {
76894
- // The items from before stay: a revalidation that failed has
77008
+ // The items from before stay, and are not asked for again until the
77009
+ // source says the collection moved: a revalidation that failed has
76895
77010
  // nothing better to put in their place.
76896
- staleRef.current = false;
77011
+ let rank = start;
77012
+ while (rank <= end) {
77013
+ staleRanksRef.current.delete(rank);
77014
+ rank++;
77015
+ }
76897
77016
  setRefreshing(false);
76898
77017
  return;
76899
77018
  }
@@ -77210,7 +77329,8 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
77210
77329
  * "Many rows"). The window keeps three quarters of what the screen leaves of
77211
77330
  * it ahead of the scroll, and moves once that falls under half a screen; it
77212
77331
  * has to hold more than the screen shows, with room for that lookahead — the
77213
- * list warns when it leaves less than two items beyond the screen.
77332
+ * list warns when it holds no more than the screen. How much more is
77333
+ * measured: fling the list on the slowest device it runs on.
77214
77334
  *
77215
77335
  * `{ initial, after }` for a list whose first picture is taken as it is
77216
77336
  * built — drawn in the click that opens a popup, or in the update callback