@jsenv/navi 0.29.442 → 0.29.443
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/dist/dev/jsenv_navi.js +293 -85
- package/dist/dev/jsenv_navi.js.map +4 -4
- package/dist/jsenv_navi.js +283 -85
- package/dist/jsenv_navi.js.map +4 -4
- package/docs/AI_INSTRUCTIONS.md +5 -4
- package/docs/mobile_touch.md +19 -6
- package/docs/scroll.md +39 -9
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -72814,15 +72814,16 @@ document.addEventListener(
|
|
|
72814
72814
|
);
|
|
72815
72815
|
|
|
72816
72816
|
/*
|
|
72817
|
-
*
|
|
72818
|
-
* progress. A write is an instant scroll, and
|
|
72819
|
-
* scroll it is playing on its own, short of
|
|
72820
|
-
* (Home, PageDown, Space), a smooth `scrollTo`
|
|
72821
|
-
* bar tap on iOS. A scroll following a hand is
|
|
72822
|
-
* turn carries on, and on most engines a
|
|
72823
|
-
* the write left it, and the fling it
|
|
72824
|
-
*
|
|
72825
|
-
* every move of a list's
|
|
72817
|
+
* Moves a scroll by what the content moved under the user, unless the write
|
|
72818
|
+
* would cost them the scroll in progress. A write is an instant scroll, and
|
|
72819
|
+
* the browser abandons for it any scroll it is playing on its own, short of
|
|
72820
|
+
* where that one was going: a key (Home, PageDown, Space), a smooth `scrollTo`
|
|
72821
|
+
* or `scrollIntoView`, the status bar tap on iOS. A scroll following a hand is
|
|
72822
|
+
* not abandoned: the wheel's next turn carries on, and on most engines a
|
|
72823
|
+
* finger keeps dragging from wherever the write left it, and the fling it
|
|
72824
|
+
* leaves behind once it lifts carries on from there too (Chrome under
|
|
72825
|
+
* DevTools' touch emulation: flings written to at every move of a list's
|
|
72826
|
+
* window went as far as flings never written to).
|
|
72826
72827
|
*
|
|
72827
72828
|
* A fling must not be taken for a scroll the browser plays on its own. A list
|
|
72828
72829
|
* then leaves uncorrected, for the whole fling, the room its fillers lose above
|
|
@@ -72849,6 +72850,18 @@ document.addEventListener(
|
|
|
72849
72850
|
* while the finger is still on one of them, and the touchend of a removed
|
|
72850
72851
|
* element never reaches the document.
|
|
72851
72852
|
*
|
|
72853
|
+
* Chrome ends a scroll at every write, a finger's fling included: "scrollend"
|
|
72854
|
+
* a frame after the write, and the fling goes on a frame later as a scroll of
|
|
72855
|
+
* its own, as far as it was going (a bare page written to every 120ms of a
|
|
72856
|
+
* fling: 9 "scrollend" for 8 writes, the fling as long). Taken for a new
|
|
72857
|
+
* scroll, it is nobody's once it starts far enough from the finger's last
|
|
72858
|
+
* move, and the list's corrections are refused for the rest of the fling: in
|
|
72859
|
+
* wematch, cards drawn above the screen pushing it down at every window slide.
|
|
72860
|
+
* So a scroll starting right after the "scrollend" of a scroll written to here
|
|
72861
|
+
* goes on as that scroll: same start, same hand. Only a written one: a scroll
|
|
72862
|
+
* starting right after one that ended on its own is a new one, a key or a
|
|
72863
|
+
* smooth `scrollTo` must not pass for the fling before it.
|
|
72864
|
+
*
|
|
72852
72865
|
* In a browser without "scrollend", no write is said to interrupt anything: a
|
|
72853
72866
|
* scroll would never be seen ending.
|
|
72854
72867
|
*/
|
|
@@ -72858,8 +72871,8 @@ document.addEventListener(
|
|
|
72858
72871
|
// hand on a scroll that came from elsewhere.
|
|
72859
72872
|
const WHEEL_REACH = 100;
|
|
72860
72873
|
// The fling Chrome plays as a scroll of its own starts a frame or two after the
|
|
72861
|
-
// drag
|
|
72862
|
-
//
|
|
72874
|
+
// "scrollend" of the drag, or of a write (DevTools' touch emulation, 4× CPU:
|
|
72875
|
+
// 12ms after the drag's, 24 to 40ms after a write's in wematch).
|
|
72863
72876
|
const FLING_REACH = 150;
|
|
72864
72877
|
// iOS's touch handling, the one engine whose finger drops writes (see above).
|
|
72865
72878
|
const FINGER_KEEPS_WRITES = !window.CSS.supports(
|
|
@@ -72868,14 +72881,41 @@ const FINGER_KEEPS_WRITES = !window.CSS.supports(
|
|
|
72868
72881
|
);
|
|
72869
72882
|
|
|
72870
72883
|
const flightMap = new WeakMap();
|
|
72884
|
+
// Scrollers written to since their last "scrollend": that "scrollend" may be
|
|
72885
|
+
// the write's.
|
|
72886
|
+
const writtenScrollerSet = new WeakSet();
|
|
72887
|
+
// The flight of a written scroller, from its "scrollend" until a scroll goes on
|
|
72888
|
+
// with it.
|
|
72889
|
+
const writtenFlightEndMap = new WeakMap();
|
|
72871
72890
|
let fingersDown = 0;
|
|
72872
72891
|
let wheelTimeStamp = -Infinity;
|
|
72873
72892
|
let fingerScrollTimeStamp = -Infinity;
|
|
72874
72893
|
|
|
72875
72894
|
/**
|
|
72895
|
+
* Answers whether it wrote.
|
|
72896
|
+
*
|
|
72876
72897
|
* @param {Element} scrollerEl
|
|
72898
|
+
* @param {number} delta
|
|
72899
|
+
* @param {{ horizontal?: boolean }} [options]
|
|
72877
72900
|
* @returns {boolean}
|
|
72878
72901
|
*/
|
|
72902
|
+
const scrollByUnlessInterrupting = (
|
|
72903
|
+
scrollerEl,
|
|
72904
|
+
delta,
|
|
72905
|
+
{ horizontal } = {},
|
|
72906
|
+
) => {
|
|
72907
|
+
if (wouldInterruptScroll(scrollerEl)) {
|
|
72908
|
+
return false;
|
|
72909
|
+
}
|
|
72910
|
+
writtenScrollerSet.add(scrollerEl);
|
|
72911
|
+
if (horizontal) {
|
|
72912
|
+
scrollerEl.scrollLeft += delta;
|
|
72913
|
+
} else {
|
|
72914
|
+
scrollerEl.scrollTop += delta;
|
|
72915
|
+
}
|
|
72916
|
+
return true;
|
|
72917
|
+
};
|
|
72918
|
+
|
|
72879
72919
|
const wouldInterruptScroll = (scrollerEl) => {
|
|
72880
72920
|
const flight = flightMap.get(scrollerEl);
|
|
72881
72921
|
if (!flight) {
|
|
@@ -72918,6 +72958,15 @@ if ("onscrollend" in window) {
|
|
|
72918
72958
|
if (flightMap.has(scroller)) {
|
|
72919
72959
|
return;
|
|
72920
72960
|
}
|
|
72961
|
+
const writtenFlightEnd = writtenFlightEndMap.get(scroller);
|
|
72962
|
+
writtenFlightEndMap.delete(scroller);
|
|
72963
|
+
if (
|
|
72964
|
+
writtenFlightEnd &&
|
|
72965
|
+
e.timeStamp - writtenFlightEnd.timeStamp < FLING_REACH
|
|
72966
|
+
) {
|
|
72967
|
+
flightMap.set(scroller, writtenFlightEnd.flight);
|
|
72968
|
+
return;
|
|
72969
|
+
}
|
|
72921
72970
|
flightMap.set(scroller, { timeStamp: e.timeStamp });
|
|
72922
72971
|
},
|
|
72923
72972
|
{ capture: true, passive: true },
|
|
@@ -72925,7 +72974,13 @@ if ("onscrollend" in window) {
|
|
|
72925
72974
|
document.addEventListener(
|
|
72926
72975
|
"scrollend",
|
|
72927
72976
|
(e) => {
|
|
72928
|
-
|
|
72977
|
+
const scroller = asScroller(e.target);
|
|
72978
|
+
const flight = flightMap.get(scroller);
|
|
72979
|
+
flightMap.delete(scroller);
|
|
72980
|
+
if (flight && writtenScrollerSet.has(scroller)) {
|
|
72981
|
+
writtenFlightEndMap.set(scroller, { flight, timeStamp: e.timeStamp });
|
|
72982
|
+
}
|
|
72983
|
+
writtenScrollerSet.delete(scroller);
|
|
72929
72984
|
},
|
|
72930
72985
|
{ capture: true, passive: true },
|
|
72931
72986
|
);
|
|
@@ -73647,13 +73702,24 @@ const createListItems = () => {
|
|
|
73647
73702
|
}
|
|
73648
73703
|
return null;
|
|
73649
73704
|
},
|
|
73650
|
-
// The room the item at that index took when it was
|
|
73651
|
-
//
|
|
73705
|
+
// The room the item at that index takes: the room it took when it was
|
|
73706
|
+
// last drawn, else the room its run is given for it (see List.Items'
|
|
73707
|
+
// itemSize), else undefined.
|
|
73652
73708
|
roomAt: (index) => {
|
|
73653
73709
|
for (const locator of locatorByOwner.values()) {
|
|
73654
|
-
const
|
|
73655
|
-
if (
|
|
73656
|
-
return
|
|
73710
|
+
const room = locator.roomAt(index);
|
|
73711
|
+
if (room !== undefined) {
|
|
73712
|
+
return room;
|
|
73713
|
+
}
|
|
73714
|
+
}
|
|
73715
|
+
return undefined;
|
|
73716
|
+
},
|
|
73717
|
+
// The room the run of that index is given for it, whatever was measured.
|
|
73718
|
+
givenRoomAt: (index) => {
|
|
73719
|
+
for (const locator of locatorByOwner.values()) {
|
|
73720
|
+
const room = locator.givenRoomAt(index);
|
|
73721
|
+
if (room !== undefined) {
|
|
73722
|
+
return room;
|
|
73657
73723
|
}
|
|
73658
73724
|
}
|
|
73659
73725
|
return undefined;
|
|
@@ -75142,6 +75208,23 @@ const css$x = /* css */`@property --list-group-label-height {
|
|
|
75142
75208
|
&[data-hidden-while-empty]:not(:has([navi-list-item-real])) {
|
|
75143
75209
|
display: none;
|
|
75144
75210
|
}
|
|
75211
|
+
|
|
75212
|
+
&[data-continued] {
|
|
75213
|
+
margin-top: calc(-1 * var(--x-list-gap, 0px));
|
|
75214
|
+
display: grid;
|
|
75215
|
+
|
|
75216
|
+
& > .navi_list_item_group_label, & > .navi_list_item_group_list {
|
|
75217
|
+
grid-area: 1 / 1;
|
|
75218
|
+
}
|
|
75219
|
+
|
|
75220
|
+
& > .navi_list_item_group_label {
|
|
75221
|
+
align-self: start;
|
|
75222
|
+
}
|
|
75223
|
+
}
|
|
75224
|
+
|
|
75225
|
+
&[data-continued="hole"] > .navi_list_item_group_label {
|
|
75226
|
+
visibility: hidden;
|
|
75227
|
+
}
|
|
75145
75228
|
}
|
|
75146
75229
|
|
|
75147
75230
|
.navi_list_container[data-horizontal] > .navi_list_scroll_container > .navi_list > .navi_list_item_group {
|
|
@@ -75962,6 +76045,7 @@ const useListScrollSync = ({
|
|
|
75962
76045
|
// that changed since is measured again when drawn again, and the anchoring
|
|
75963
76046
|
// holds the view across the difference. Items laid side by side in a grid
|
|
75964
76047
|
// share a line, which the fillers hold at the one size.
|
|
76048
|
+
const givenRoomWarnedRef = useRef(false);
|
|
75965
76049
|
const rememberItemRooms = () => {
|
|
75966
76050
|
if (!ref.current || itemsPerLineRef.current !== 1) {
|
|
75967
76051
|
return;
|
|
@@ -75970,7 +76054,22 @@ const useListScrollSync = ({
|
|
|
75970
76054
|
if (!listEl) {
|
|
75971
76055
|
return;
|
|
75972
76056
|
}
|
|
75973
|
-
readItemRooms(listEl, horizontal,
|
|
76057
|
+
readItemRooms(listEl, horizontal, (item, room, complete) => {
|
|
76058
|
+
listItems.roomById.set(item.id, room);
|
|
76059
|
+
if (!complete || givenRoomWarnedRef.current) {
|
|
76060
|
+
return;
|
|
76061
|
+
}
|
|
76062
|
+
const givenRoom = listItems.givenRoomAt(item.index);
|
|
76063
|
+
if (givenRoom === undefined) {
|
|
76064
|
+
return;
|
|
76065
|
+
}
|
|
76066
|
+
const difference = room - givenRoom;
|
|
76067
|
+
if (difference > -0.5 && difference < 0.5) {
|
|
76068
|
+
return;
|
|
76069
|
+
}
|
|
76070
|
+
givenRoomWarnedRef.current = true;
|
|
76071
|
+
console.warn(`List.Items itemSize gives ${givenRoom}px to the item at index ${item.index}, which took ${Math.round(room * 10) / 10}px from the end of the item before it to its own end. The items not drawn are held at the room itemSize gives, so the screen moves by the difference when they are drawn above it — and nothing corrects it during a fling on iOS.`);
|
|
76072
|
+
});
|
|
75974
76073
|
};
|
|
75975
76074
|
const updateRenderWindow = (newStart, newEnd, reason) => {
|
|
75976
76075
|
const {
|
|
@@ -76732,18 +76831,14 @@ const useListScrollSync = ({
|
|
|
76732
76831
|
if (delta > -0.5 && delta < 0.5) {
|
|
76733
76832
|
return;
|
|
76734
76833
|
}
|
|
76735
|
-
|
|
76736
|
-
|
|
76737
|
-
|
|
76738
|
-
|
|
76739
|
-
|
|
76740
|
-
|
|
76741
|
-
}
|
|
76742
|
-
|
|
76743
|
-
if (horizontal) {
|
|
76744
|
-
scrollerEl.scrollLeft += delta;
|
|
76745
|
-
} else {
|
|
76746
|
-
scrollerEl.scrollTop += delta;
|
|
76834
|
+
// Not into a scroll it would cut short, as the anchoring's write (see
|
|
76835
|
+
// holdAnchorStill). The document's box is the scroller of a list
|
|
76836
|
+
// scrolling the page, and it resizes with every item drawn taller than
|
|
76837
|
+
// its filler: while the page scrolls, that is at every window move.
|
|
76838
|
+
if (scrollByUnlessInterrupting(scrollerEl, delta, {
|
|
76839
|
+
horizontal
|
|
76840
|
+
})) {
|
|
76841
|
+
anchorRef.current = null;
|
|
76747
76842
|
}
|
|
76748
76843
|
};
|
|
76749
76844
|
const observer = new ResizeObserver(entries => {
|
|
@@ -76836,19 +76931,15 @@ const useListScrollSync = ({
|
|
|
76836
76931
|
return;
|
|
76837
76932
|
}
|
|
76838
76933
|
anchorRef.current = null;
|
|
76839
|
-
|
|
76840
|
-
|
|
76841
|
-
|
|
76842
|
-
|
|
76843
|
-
|
|
76844
|
-
|
|
76845
|
-
|
|
76846
|
-
}
|
|
76847
|
-
|
|
76848
|
-
if (horizontal) {
|
|
76849
|
-
scrollerEl.scrollLeft += drift;
|
|
76850
|
-
} else {
|
|
76851
|
-
scrollerEl.scrollTop += drift;
|
|
76934
|
+
// Not written into a scroll nobody is looking at: its items are passing
|
|
76935
|
+
// by, on their way somewhere the browser takes them (a key, a smooth
|
|
76936
|
+
// scrollTo, the status bar, a fling on iOS). The write would stop that
|
|
76937
|
+
// scroll short of where it was going. Left alone, the items drawn above
|
|
76938
|
+
// push the rest by `drift` while all of it moves, and the scroll lands.
|
|
76939
|
+
if (scrollByUnlessInterrupting(scrollerEl, drift, {
|
|
76940
|
+
horizontal
|
|
76941
|
+
})) {
|
|
76942
|
+
scrolledByListRef.current = true;
|
|
76852
76943
|
}
|
|
76853
76944
|
};
|
|
76854
76945
|
useLayoutEffect(holdAnchorStill);
|
|
@@ -77983,18 +78074,17 @@ const readWindowGeometry = (scrollerEl, listEl, horizontal) => {
|
|
|
77983
78074
|
};
|
|
77984
78075
|
};
|
|
77985
78076
|
// The room each item drawn takes along the scroll axis (see
|
|
77986
|
-
// rememberItemRooms):
|
|
77987
|
-
// what stands between
|
|
77988
|
-
// opens — is counted
|
|
77989
|
-
//
|
|
77990
|
-
|
|
78077
|
+
// rememberItemRooms): from where the item before it ends to where it ends, so
|
|
78078
|
+
// that what stands between the two — a separator, the label of the group it
|
|
78079
|
+
// opens — is counted with the item it comes with. A group the window cuts
|
|
78080
|
+
// draws its label without room (see the data-continued rule above): that
|
|
78081
|
+
// label's room is held by the item that opens the group, in the filler. The
|
|
78082
|
+
// first item drawn is measured from the end of what stands before it in the
|
|
78083
|
+
// list (a filler, a header); a stand-in is not measured, it takes the room it
|
|
78084
|
+
// is given and not the room of its item, but an item after it ends where it
|
|
78085
|
+
// ends. `complete` says whether the room was read up to an end before it.
|
|
78086
|
+
const readItemRooms = (listEl, horizontal, onRoom) => {
|
|
77991
78087
|
let previous = null;
|
|
77992
|
-
const keepRoom = (item, next) => {
|
|
77993
|
-
if (item.id === null) {
|
|
77994
|
-
return;
|
|
77995
|
-
}
|
|
77996
|
-
roomById.set(item.id, next && next.index === item.index + 1 ? next.from - item.from : item.to - item.from);
|
|
77997
|
-
};
|
|
77998
78088
|
for (const itemEl of listEl.querySelectorAll(`[${LIST_ITEM_INDEX_ATTRIBUTE}]`)) {
|
|
77999
78089
|
if (!isOwnItem(itemEl, listEl)) {
|
|
78000
78090
|
continue;
|
|
@@ -78009,14 +78099,41 @@ const readItemRooms = (listEl, horizontal, roomById) => {
|
|
|
78009
78099
|
from: horizontal ? rect.left : rect.top,
|
|
78010
78100
|
to: horizontal ? rect.right : rect.bottom
|
|
78011
78101
|
};
|
|
78012
|
-
if (
|
|
78013
|
-
|
|
78102
|
+
if (item.id !== null) {
|
|
78103
|
+
const endBefore = previous && previous.index === item.index - 1 ? previous.to : readEndBefore(itemEl, listEl, horizontal);
|
|
78104
|
+
if (endBefore === null) {
|
|
78105
|
+
onRoom(item, item.to - item.from, false);
|
|
78106
|
+
} else {
|
|
78107
|
+
onRoom(item, item.to - endBefore, true);
|
|
78108
|
+
}
|
|
78014
78109
|
}
|
|
78015
78110
|
previous = item;
|
|
78016
78111
|
}
|
|
78017
|
-
|
|
78018
|
-
|
|
78112
|
+
};
|
|
78113
|
+
// Where what stands before an item in the list ends: the element before the
|
|
78114
|
+
// item, or before its group when it is the first of it — or the start of the
|
|
78115
|
+
// list's content when nothing does. Null when the element before is not laid
|
|
78116
|
+
// out.
|
|
78117
|
+
const readEndBefore = (itemEl, listEl, horizontal) => {
|
|
78118
|
+
let slotEl = itemEl;
|
|
78119
|
+
const groupListEl = itemEl.parentElement;
|
|
78120
|
+
if (groupListEl && groupListEl.classList.contains("navi_list_item_group_list") && groupListEl.firstElementChild === itemEl) {
|
|
78121
|
+
slotEl = groupListEl.closest(".navi_list_item_group") || itemEl;
|
|
78122
|
+
}
|
|
78123
|
+
const beforeEl = slotEl.previousElementSibling;
|
|
78124
|
+
if (beforeEl) {
|
|
78125
|
+
const rect = beforeEl.getBoundingClientRect();
|
|
78126
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
78127
|
+
return null;
|
|
78128
|
+
}
|
|
78129
|
+
return horizontal ? rect.right : rect.bottom;
|
|
78019
78130
|
}
|
|
78131
|
+
if (slotEl.parentElement !== listEl) {
|
|
78132
|
+
return null;
|
|
78133
|
+
}
|
|
78134
|
+
const listRect = listEl.getBoundingClientRect();
|
|
78135
|
+
const listStyle = window.getComputedStyle(listEl);
|
|
78136
|
+
return horizontal ? listRect.left + parseFloat(listStyle.borderLeftWidth) + parseFloat(listStyle.paddingLeft) : listRect.top + parseFloat(listStyle.borderTopWidth) + parseFloat(listStyle.paddingTop);
|
|
78020
78137
|
};
|
|
78021
78138
|
// The first item the screen shows from `position` on, and the item after the
|
|
78022
78139
|
// last one it shows up to `position` (exclusive, like the window's end). An
|
|
@@ -78566,6 +78683,8 @@ const UnorderedList = ({
|
|
|
78566
78683
|
grid: trackColumns ? true : undefined,
|
|
78567
78684
|
gridTemplateColumns: trackColumns,
|
|
78568
78685
|
...rest,
|
|
78686
|
+
// The gap a group the window cuts takes back (see data-continued).
|
|
78687
|
+
style: withListGap(rest.style, spacing),
|
|
78569
78688
|
spacing: spacing,
|
|
78570
78689
|
baseClassName: "navi_list",
|
|
78571
78690
|
children: [!suppressFallback && searchFallbackShown && jsx(SearchFallback, {
|
|
@@ -78598,6 +78717,21 @@ const UnorderedList = ({
|
|
|
78598
78717
|
})]
|
|
78599
78718
|
});
|
|
78600
78719
|
};
|
|
78720
|
+
// The list's spacing as a variable, next to the style it is given (an object
|
|
78721
|
+
// or a css string, as Box takes either).
|
|
78722
|
+
const withListGap = (style, spacing) => {
|
|
78723
|
+
if (spacing === undefined) {
|
|
78724
|
+
return style;
|
|
78725
|
+
}
|
|
78726
|
+
const gap = stringifySpacingStyle(spacing, "gap");
|
|
78727
|
+
if (typeof style === "string") {
|
|
78728
|
+
return `${style};--x-list-gap:${gap}`;
|
|
78729
|
+
}
|
|
78730
|
+
return {
|
|
78731
|
+
...style,
|
|
78732
|
+
"--x-list-gap": gap
|
|
78733
|
+
};
|
|
78734
|
+
};
|
|
78601
78735
|
|
|
78602
78736
|
// The "no match" message. Whether it shows is decided by ListUI (see its
|
|
78603
78737
|
// searchFallbackShown): a search left nothing to display, and the caller did
|
|
@@ -78635,19 +78769,20 @@ const Fallback = ({
|
|
|
78635
78769
|
// every item it draws must not be redrawn — every item of it — because the size
|
|
78636
78770
|
// settled after the first commit.
|
|
78637
78771
|
// It holds lines: the items side by side on one take its room together, and a
|
|
78638
|
-
// last line left short takes it all the same. An item
|
|
78639
|
-
//
|
|
78772
|
+
// last line left short takes it all the same. An item whose room is known takes
|
|
78773
|
+
// that room instead: the one it took when the list drew it before (see
|
|
78774
|
+
// rememberItemRooms), or the one its run is given (see List.Items' itemSize).
|
|
78640
78775
|
const VirtualFiller = ({
|
|
78641
78776
|
edge,
|
|
78642
78777
|
itemCount,
|
|
78643
78778
|
itemsPerLine,
|
|
78644
|
-
|
|
78645
|
-
|
|
78779
|
+
itemsKnown,
|
|
78780
|
+
roomKnown,
|
|
78646
78781
|
findChunks
|
|
78647
78782
|
}) => {
|
|
78648
78783
|
const listItems = useContext(ListItemsContext);
|
|
78649
78784
|
const lineSize = listItems.virtualItemSizeSignal.value;
|
|
78650
|
-
const sizeToFill = Math.ceil(itemCount / itemsPerLine) * lineSize -
|
|
78785
|
+
const sizeToFill = Math.ceil(itemCount / itemsPerLine) * lineSize - itemsKnown * lineSize / itemsPerLine + roomKnown;
|
|
78651
78786
|
// A filler resizing moves what stands below it — the items on screen, when it
|
|
78652
78787
|
// holds the room of items above them — and it resizes in a commit of its own
|
|
78653
78788
|
// when the item size settles after the list has rendered: the list puts its
|
|
@@ -79364,6 +79499,7 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
79364
79499
|
* pageSize?: number,
|
|
79365
79500
|
* memoryBudget?: number,
|
|
79366
79501
|
* renderSkeleton?: false | ((index: number) => import("ignore:preact").ComponentChildren),
|
|
79502
|
+
* itemSize?: (index: number) => number,
|
|
79367
79503
|
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("ignore:preact").ComponentChildren,
|
|
79368
79504
|
* onRequestStateChange?: (state: {busy: boolean, refreshing: boolean, range: {start: number, end: number}|null}) => void,
|
|
79369
79505
|
* }>}
|
|
@@ -79396,6 +79532,10 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
79396
79532
|
* `<List.Group>` whose label (`renderGroupLabel`) stays on screen for as long
|
|
79397
79533
|
* as one of them is. The groups are found in the data as it arrives, which is
|
|
79398
79534
|
* the only way a list that discovers its items page by page can have any.
|
|
79535
|
+
* Style the label, not the group element: a group the render window cuts is
|
|
79536
|
+
* laid out by the list, its label over its first items, so that the label
|
|
79537
|
+
* and the gap before it — held in the room of the item opening the group —
|
|
79538
|
+
* take no room twice.
|
|
79399
79539
|
* @param {(item: any, index: number) => any} [props.renderGroupLabel]
|
|
79400
79540
|
* The label of the group an item opens, given that item.
|
|
79401
79541
|
* @param {(item: any, index: number) => object} [props.groupLabelProps]
|
|
@@ -79414,9 +79554,26 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
79414
79554
|
* further out. `Infinity` keeps every item the run ever received; `0` keeps
|
|
79415
79555
|
* only the ones around the window.
|
|
79416
79556
|
* @param {false|(index: number) => any} [props.renderSkeleton]
|
|
79417
|
-
* What to draw for an item the run does not hold
|
|
79557
|
+
* What to draw for an item the run does not hold, at the room that item will
|
|
79558
|
+
* take: a screen of skeletons alone has no item for the list to hold still,
|
|
79559
|
+
* and when the page lands, what is below a skeleton of the wrong size moves
|
|
79560
|
+
* by the difference (see docs/scroll.md). Defaults to List's own
|
|
79418
79561
|
* `renderSkeleton`, then to a bare `<List.Item skeleton>`; `false` leaves the
|
|
79419
79562
|
* item empty (its room is still held, or the list would jump as it loads).
|
|
79563
|
+
* @param {(index: number) => number} [props.itemSize]
|
|
79564
|
+
* The room the item at an index takes along the scroll axis, in px, known
|
|
79565
|
+
* before the item arrives: from where the item before it ends to where it
|
|
79566
|
+
* ends — what stands between the two is its own (the gap, a separator, the
|
|
79567
|
+
* label of the group it opens; the first item of the list has no gap before
|
|
79568
|
+
* it). The fillers then hold every item the list has not drawn at its own
|
|
79569
|
+
* room instead of List's `virtualItemSize`, and an item drawn above the
|
|
79570
|
+
* screen takes exactly the room held for it: nothing on screen moves. It is
|
|
79571
|
+
* the only way the screen holds still during a fling on iOS, where writing
|
|
79572
|
+
* the scroll to correct it would stop the fling. The same index
|
|
79573
|
+
* `renderSkeleton` is given — the two draw on the same knowledge. A room
|
|
79574
|
+
* measured on an item drawn wins over it, and in development one that
|
|
79575
|
+
* differs from it is reported. Items side by side in a grid (`columns`)
|
|
79576
|
+
* share a line, which keeps `virtualItemSize`.
|
|
79420
79577
|
* @param {(failure: {error: any, retry: () => void, start: number, end: number}) => any} [props.renderError]
|
|
79421
79578
|
* What to draw where items were asked for and never came: given the `error`,
|
|
79422
79579
|
* a `retry` to call, and the `start`/`end` of the range that failed — the
|
|
@@ -79489,6 +79646,7 @@ const useRunItems = (store, {
|
|
|
79489
79646
|
renderItem,
|
|
79490
79647
|
findText,
|
|
79491
79648
|
pageSize,
|
|
79649
|
+
itemSize,
|
|
79492
79650
|
groupBy,
|
|
79493
79651
|
renderGroupLabel,
|
|
79494
79652
|
groupLabelProps,
|
|
@@ -79572,6 +79730,22 @@ const useRunItems = (store, {
|
|
|
79572
79730
|
const item = getItemAt(itemIndex);
|
|
79573
79731
|
return item === undefined ? undefined : idOf(item, itemIndex);
|
|
79574
79732
|
};
|
|
79733
|
+
// The room the caller says an item takes (`itemSize`), drawn or not.
|
|
79734
|
+
const givenRoomOf = itemIndex => {
|
|
79735
|
+
if (!itemSize || itemsPerLine !== 1 || itemIndex < runStart || itemIndex >= runEnd) {
|
|
79736
|
+
return undefined;
|
|
79737
|
+
}
|
|
79738
|
+
const room = itemSize(itemIndex);
|
|
79739
|
+
return room > 0 ? room : undefined;
|
|
79740
|
+
};
|
|
79741
|
+
// The room an item took when the list last drew it (see rememberItemRooms),
|
|
79742
|
+
// else the room the caller gives it, else undefined: a filler holds an item
|
|
79743
|
+
// it knows nothing about at the item size.
|
|
79744
|
+
const roomOf = itemIndex => {
|
|
79745
|
+
const id = idAt(itemIndex);
|
|
79746
|
+
const remembered = id === undefined ? undefined : listItems.roomById.get(id);
|
|
79747
|
+
return remembered === undefined ? givenRoomOf(itemIndex) : remembered;
|
|
79748
|
+
};
|
|
79575
79749
|
listItems.setItemLocator(ownerId, {
|
|
79576
79750
|
indexOf: id => {
|
|
79577
79751
|
let found = null;
|
|
@@ -79583,32 +79757,27 @@ const useRunItems = (store, {
|
|
|
79583
79757
|
});
|
|
79584
79758
|
return found;
|
|
79585
79759
|
},
|
|
79586
|
-
|
|
79760
|
+
roomAt: roomOf,
|
|
79761
|
+
givenRoomAt: givenRoomOf
|
|
79587
79762
|
});
|
|
79588
|
-
|
|
79589
|
-
|
|
79590
|
-
const roomOf = itemIndex => {
|
|
79591
|
-
const id = idAt(itemIndex);
|
|
79592
|
-
return id === undefined ? undefined : listItems.roomById.get(id);
|
|
79593
|
-
};
|
|
79594
|
-
const readRemembered = (from, to) => {
|
|
79595
|
-
const remembered = {
|
|
79763
|
+
const readKnownRooms = (from, to) => {
|
|
79764
|
+
const known = {
|
|
79596
79765
|
room: 0,
|
|
79597
79766
|
count: 0
|
|
79598
79767
|
};
|
|
79599
|
-
if (listItems.roomById.size === 0) {
|
|
79600
|
-
return
|
|
79768
|
+
if (listItems.roomById.size === 0 && !itemSize) {
|
|
79769
|
+
return known;
|
|
79601
79770
|
}
|
|
79602
79771
|
let itemIndex = from;
|
|
79603
79772
|
while (itemIndex < to) {
|
|
79604
79773
|
const room = roomOf(itemIndex);
|
|
79605
79774
|
if (room !== undefined) {
|
|
79606
|
-
|
|
79607
|
-
|
|
79775
|
+
known.room += room;
|
|
79776
|
+
known.count++;
|
|
79608
79777
|
}
|
|
79609
79778
|
itemIndex++;
|
|
79610
79779
|
}
|
|
79611
|
-
return
|
|
79780
|
+
return known;
|
|
79612
79781
|
};
|
|
79613
79782
|
useLayoutEffect(() => {
|
|
79614
79783
|
return () => {
|
|
@@ -79729,6 +79898,7 @@ const useRunItems = (store, {
|
|
|
79729
79898
|
nodes.push(jsx(ListItemGroup, {
|
|
79730
79899
|
label: group.label,
|
|
79731
79900
|
labelProps: group.labelProps,
|
|
79901
|
+
continued: group.continued,
|
|
79732
79902
|
children: group.children
|
|
79733
79903
|
}, groupCount === 1 ? `${ownerId}_group_${group.key}` : `${ownerId}_group_${group.key}_${groupCount}`));
|
|
79734
79904
|
group = null;
|
|
@@ -79766,10 +79936,38 @@ const useRunItems = (store, {
|
|
|
79766
79936
|
}
|
|
79767
79937
|
return groupBy(item, itemIndex);
|
|
79768
79938
|
};
|
|
79939
|
+
// Where a group opened at that item began, when not there (see
|
|
79940
|
+
// data-continued): "above" the window, or in the "hole" of items not held
|
|
79941
|
+
// drawn right before it. Items not held belong to the group they lead to as
|
|
79942
|
+
// often as not — a page lands every hundred items, a group starts a few
|
|
79943
|
+
// times in a thread — so a group the window starts in is taken to go on
|
|
79944
|
+
// above it unless the item before, held, says otherwise.
|
|
79945
|
+
// Along y only, never built along x: there a group stands its label over
|
|
79946
|
+
// its items, so the label takes no room on the axis (unless it is wider than
|
|
79947
|
+
// the items drawn) and only the gap would be taken back — `margin-left`,
|
|
79948
|
+
// without the overlay of the data-continued rule. Nothing measured it yet.
|
|
79949
|
+
let afterHole = false;
|
|
79950
|
+
const whereGroupContinues = (groupKey, itemIndex) => {
|
|
79951
|
+
if (listItems.horizontal) {
|
|
79952
|
+
return undefined;
|
|
79953
|
+
}
|
|
79954
|
+
if (afterHole) {
|
|
79955
|
+
return "hole";
|
|
79956
|
+
}
|
|
79957
|
+
if (itemIndex !== windowFrom || windowFrom <= runStart) {
|
|
79958
|
+
return undefined;
|
|
79959
|
+
}
|
|
79960
|
+
const itemBefore = getItemAt(windowFrom - 1);
|
|
79961
|
+
if (itemBefore === undefined || groupBy(itemBefore, windowFrom - 1) === groupKey) {
|
|
79962
|
+
return "above";
|
|
79963
|
+
}
|
|
79964
|
+
return undefined;
|
|
79965
|
+
};
|
|
79769
79966
|
const pushItem = (itemNode, item, itemIndex, groupKey) => {
|
|
79770
79967
|
if (groupKey === undefined) {
|
|
79771
79968
|
closeGroup();
|
|
79772
79969
|
nodes.push(itemNode);
|
|
79970
|
+
afterHole = item === undefined;
|
|
79773
79971
|
return;
|
|
79774
79972
|
}
|
|
79775
79973
|
if (!group || group.key !== groupKey) {
|
|
@@ -79778,9 +79976,11 @@ const useRunItems = (store, {
|
|
|
79778
79976
|
key: groupKey,
|
|
79779
79977
|
label: renderGroupLabel ? renderGroupLabel(item, itemIndex) : groupKey,
|
|
79780
79978
|
labelProps: groupLabelProps ? groupLabelProps(item, itemIndex) : undefined,
|
|
79979
|
+
continued: whereGroupContinues(groupKey, itemIndex),
|
|
79781
79980
|
children: []
|
|
79782
79981
|
};
|
|
79783
79982
|
}
|
|
79983
|
+
afterHole = false;
|
|
79784
79984
|
group.children.push(itemNode);
|
|
79785
79985
|
};
|
|
79786
79986
|
// The text of the items a filler stands for, cut in chunks aligned on the
|
|
@@ -79823,13 +80023,13 @@ const useRunItems = (store, {
|
|
|
79823
80023
|
// one run, and what sits before or after it (a header, items given one by
|
|
79824
80024
|
// one) is not virtualized at all.
|
|
79825
80025
|
if (windowFrom > runStart) {
|
|
79826
|
-
const
|
|
80026
|
+
const known = readKnownRooms(runStart, windowFrom);
|
|
79827
80027
|
nodes.push(jsx(VirtualFiller, {
|
|
79828
80028
|
edge: "before",
|
|
79829
80029
|
itemCount: windowFrom - runStart,
|
|
79830
80030
|
itemsPerLine: itemsPerLine,
|
|
79831
|
-
|
|
79832
|
-
|
|
80031
|
+
itemsKnown: known.count,
|
|
80032
|
+
roomKnown: known.room,
|
|
79833
80033
|
findChunks: getFindChunks(runStart, windowFrom)
|
|
79834
80034
|
}, "navi-list-filler-before"));
|
|
79835
80035
|
}
|
|
@@ -79856,6 +80056,7 @@ const useRunItems = (store, {
|
|
|
79856
80056
|
retry: store.retry
|
|
79857
80057
|
})
|
|
79858
80058
|
}, `${ownerId}_failure_${failureFrom}`));
|
|
80059
|
+
afterHole = false;
|
|
79859
80060
|
itemIndex = failureTo + 1;
|
|
79860
80061
|
continue;
|
|
79861
80062
|
}
|
|
@@ -79931,13 +80132,13 @@ const useRunItems = (store, {
|
|
|
79931
80132
|
}
|
|
79932
80133
|
closeGroup();
|
|
79933
80134
|
if (runEnd > windowTo) {
|
|
79934
|
-
const
|
|
80135
|
+
const known = readKnownRooms(windowTo, runEnd);
|
|
79935
80136
|
nodes.push(jsx(VirtualFiller, {
|
|
79936
80137
|
edge: "after",
|
|
79937
80138
|
itemCount: runEnd - windowTo,
|
|
79938
80139
|
itemsPerLine: itemsPerLine,
|
|
79939
|
-
|
|
79940
|
-
|
|
80140
|
+
itemsKnown: known.count,
|
|
80141
|
+
roomKnown: known.room,
|
|
79941
80142
|
findChunks: getFindChunks(windowTo, runEnd)
|
|
79942
80143
|
}, "navi-list-filler-after"));
|
|
79943
80144
|
}
|
|
@@ -80591,12 +80792,17 @@ const useItemStore = ({
|
|
|
80591
80792
|
* hiddenWhileEmpty — the group leaves the flow (`display: none`) while it
|
|
80592
80793
|
* holds no real item — a search that emptied it, items not
|
|
80593
80794
|
* arrived yet
|
|
80795
|
+
* continued — set by a run of items, not by callers: the group began
|
|
80796
|
+
* before the first item drawn ("above" the render window,
|
|
80797
|
+
* or in a "hole" of items not held), and what stands
|
|
80798
|
+
* before that item takes no room (see data-continued)
|
|
80594
80799
|
* ...rest — forwarded to the outer <li role="presentation">
|
|
80595
80800
|
*/
|
|
80596
80801
|
const ListItemGroup = ({
|
|
80597
80802
|
label,
|
|
80598
80803
|
labelProps,
|
|
80599
80804
|
hiddenWhileEmpty,
|
|
80805
|
+
continued,
|
|
80600
80806
|
children,
|
|
80601
80807
|
...rest
|
|
80602
80808
|
}) => {
|
|
@@ -80639,6 +80845,7 @@ const ListItemGroup = ({
|
|
|
80639
80845
|
baseClassName: "navi_list_item_group",
|
|
80640
80846
|
role: "presentation",
|
|
80641
80847
|
"data-hidden-while-empty": hiddenWhileEmpty ? "" : undefined,
|
|
80848
|
+
"data-continued": continued,
|
|
80642
80849
|
children: [jsx("span", {
|
|
80643
80850
|
...labelRest,
|
|
80644
80851
|
ref: labelRef,
|
|
@@ -80885,7 +81092,8 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
80885
81092
|
* the fillers hold for it outside the render window, what a scroll position
|
|
80886
81093
|
* inside them is read with, and the least an item on its way takes (see
|
|
80887
81094
|
* `renderSkeleton`). An item the list has drawn is held at the room it took
|
|
80888
|
-
* then
|
|
81095
|
+
* then, and an item whose run says its room (`<List.Items itemSize>`) at
|
|
81096
|
+
* that room. Left out, it is the average of the items measured so far — once when
|
|
80889
81097
|
* the list mounts, again when a popup around it opens, and after each commit
|
|
80890
81098
|
* while items are held off screen. Given, it is the worst case: the size
|
|
80891
81099
|
* every item has, or, in a list whose items differ, the smallest an item can
|