@jsenv/navi 0.29.447 → 0.29.449
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 +324 -86
- package/dist/dev/jsenv_navi.js.map +3 -3
- package/dist/jsenv_navi.js +304 -82
- package/dist/jsenv_navi.js.map +3 -3
- package/docs/AI_INSTRUCTIONS.md +2 -1
- package/docs/resource.md +38 -7
- package/docs/scroll.md +7 -3
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -4827,9 +4827,11 @@ const actionRunEffect = (
|
|
|
4827
4827
|
return params;
|
|
4828
4828
|
});
|
|
4829
4829
|
// Whether the getter asks for a run is read off what it returned, never off
|
|
4830
|
-
// the
|
|
4831
|
-
//
|
|
4832
|
-
//
|
|
4830
|
+
// the instances it moves between: an action handed here holding params of
|
|
4831
|
+
// its own (an instance, a binding) keeps them when the getter returns
|
|
4832
|
+
// nothing — and nothing must still run nothing; an instance reset under the
|
|
4833
|
+
// effect (a same-resource DELETE) reads as never run — and the next params
|
|
4834
|
+
// the getter asks for must still run.
|
|
4833
4835
|
let asking = Boolean(actionParamsSignal.peek());
|
|
4834
4836
|
const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
|
|
4835
4837
|
debounce,
|
|
@@ -4856,11 +4858,7 @@ const actionRunEffect = (
|
|
|
4856
4858
|
return;
|
|
4857
4859
|
}
|
|
4858
4860
|
|
|
4859
|
-
if (
|
|
4860
|
-
actionTargetPrevious &&
|
|
4861
|
-
!actionTargetPrevious.isPrerun &&
|
|
4862
|
-
actionTarget
|
|
4863
|
-
) {
|
|
4861
|
+
if (actionTargetPrevious && actionTarget) {
|
|
4864
4862
|
// params changed
|
|
4865
4863
|
if (!asking) {
|
|
4866
4864
|
// falsy params, don't run
|
|
@@ -73595,6 +73593,7 @@ const css$x = /* css */`@property --list-group-label-height {
|
|
|
73595
73593
|
}
|
|
73596
73594
|
|
|
73597
73595
|
.navi_list {
|
|
73596
|
+
--x-list-item-between: 0px;
|
|
73598
73597
|
box-sizing: border-box;
|
|
73599
73598
|
outline: none;
|
|
73600
73599
|
margin: 0;
|
|
@@ -74568,6 +74567,32 @@ const useListScrollSync = ({
|
|
|
74568
74567
|
}
|
|
74569
74568
|
});
|
|
74570
74569
|
|
|
74570
|
+
// What stands between two items along the axis — the gap, a separator and
|
|
74571
|
+
// the gaps around it — which an item on its way takes out of the room it is
|
|
74572
|
+
// given (see getSkeletonItem): virtualItemSize is an item's room, from the
|
|
74573
|
+
// end of the item before to its own end, and a skeleton whose box took all
|
|
74574
|
+
// of it would be taller than the item replacing it, moving everything below
|
|
74575
|
+
// by the difference. Read before the anchoring of this commit runs, and
|
|
74576
|
+
// written to the list's own box so the skeletons follow without rendering.
|
|
74577
|
+
// Items side by side in a grid share a line: none stands between them.
|
|
74578
|
+
useLayoutEffect(() => {
|
|
74579
|
+
if (columns || !ref.current || !listItems.hasRuns()) {
|
|
74580
|
+
return;
|
|
74581
|
+
}
|
|
74582
|
+
const listEl = getListEl();
|
|
74583
|
+
if (!listEl) {
|
|
74584
|
+
return;
|
|
74585
|
+
}
|
|
74586
|
+
const spaceBetween = readSpaceBetweenItems(listEl, horizontal);
|
|
74587
|
+
if (spaceBetween === null) {
|
|
74588
|
+
return;
|
|
74589
|
+
}
|
|
74590
|
+
const value = `${spaceBetween}px`;
|
|
74591
|
+
if (listEl.style.getPropertyValue("--x-list-item-between") !== value) {
|
|
74592
|
+
listEl.style.setProperty("--x-list-item-between", value);
|
|
74593
|
+
}
|
|
74594
|
+
});
|
|
74595
|
+
|
|
74571
74596
|
// What the window holds goes through three stages (see settleWindow):
|
|
74572
74597
|
// - "first": the first commit, a count of items since nothing is laid out
|
|
74573
74598
|
// yet to measure a size with — `initial` or the budget when they are one,
|
|
@@ -76011,14 +76036,16 @@ const canScrollerScroll = (scrollerEl, axis) => {
|
|
|
76011
76036
|
return scrollSize - clientSize > 1;
|
|
76012
76037
|
};
|
|
76013
76038
|
// What a sticky part of the list sticks to is the nearest scroll container in
|
|
76014
|
-
// the DOM; `scroller` has no say in it. A list
|
|
76015
|
-
// therefore have its group labels and its header stuck to a wrapper
|
|
76016
|
-
//
|
|
76017
|
-
//
|
|
76018
|
-
//
|
|
76019
|
-
//
|
|
76039
|
+
// the DOM; `scroller` has no say in it. A list scrolled by a box around it can
|
|
76040
|
+
// therefore have its group labels and its header stuck to a wrapper between
|
|
76041
|
+
// the two that never scrolls along that axis — and pushed down by that
|
|
76042
|
+
// wrapper's scroll-padding on top of it. The usual culprit is a wrapper
|
|
76043
|
+
// carrying `overflow-x: auto`: to keep the document from overflowing
|
|
76044
|
+
// horizontally on mobile, where `overflow-x: clip` keeps that guarantee without
|
|
76045
|
+
// making a scroll container, or to let a wide table scroll sideways, where the
|
|
76046
|
+
// header cannot stick to both boxes.
|
|
76020
76047
|
const STICKY_LIST_PART_SELECTOR = `.navi_list_item_header, .navi_list_item_footer, .navi_list_item_group_label`;
|
|
76021
|
-
const useStickyScrollportWarning = (ref, scroller) => {
|
|
76048
|
+
const useStickyScrollportWarning = (ref, scroller, getScroller, horizontal) => {
|
|
76022
76049
|
useRef(false);
|
|
76023
76050
|
useLayoutEffect(() => {
|
|
76024
76051
|
{
|
|
@@ -76492,6 +76519,32 @@ const findItemsFrom = (listEl, from, horizontal) => {
|
|
|
76492
76519
|
// items (a card holding a list of its own): both carry the same attributes.
|
|
76493
76520
|
// Groups nest the items in a list of their own class, not in another list.
|
|
76494
76521
|
const isOwnItem = (itemEl, listEl) => itemEl.closest(".navi_list") === listEl;
|
|
76522
|
+
// From where an item ends to where the item after it starts, read on the first
|
|
76523
|
+
// two that follow each other in the same container (a group's label stands
|
|
76524
|
+
// between two items of different groups). Null when no two items do.
|
|
76525
|
+
const readSpaceBetweenItems = (listEl, horizontal) => {
|
|
76526
|
+
let previous = null;
|
|
76527
|
+
for (const itemEl of listEl.querySelectorAll(`[${LIST_ITEM_INDEX_ATTRIBUTE}]`)) {
|
|
76528
|
+
if (!isOwnItem(itemEl, listEl)) {
|
|
76529
|
+
continue;
|
|
76530
|
+
}
|
|
76531
|
+
const rect = itemEl.getBoundingClientRect();
|
|
76532
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
76533
|
+
previous = null;
|
|
76534
|
+
continue;
|
|
76535
|
+
}
|
|
76536
|
+
const index = Number(itemEl.getAttribute(LIST_ITEM_INDEX_ATTRIBUTE));
|
|
76537
|
+
if (previous && previous.index === index - 1 && previous.containerEl === itemEl.parentElement) {
|
|
76538
|
+
return (horizontal ? rect.left : rect.top) - previous.to;
|
|
76539
|
+
}
|
|
76540
|
+
previous = {
|
|
76541
|
+
index,
|
|
76542
|
+
containerEl: itemEl.parentElement,
|
|
76543
|
+
to: horizontal ? rect.right : rect.bottom
|
|
76544
|
+
};
|
|
76545
|
+
}
|
|
76546
|
+
return null;
|
|
76547
|
+
};
|
|
76495
76548
|
// The item drawn at an index of the list, a stand-in or a real one.
|
|
76496
76549
|
const findItemElementAt = (listEl, index) => {
|
|
76497
76550
|
for (const itemEl of listEl.querySelectorAll(`[${LIST_ITEM_INDEX_ATTRIBUTE}="${index}"]`)) {
|
|
@@ -76607,7 +76660,10 @@ const readItemRooms = (listEl, horizontal, onRoom) => {
|
|
|
76607
76660
|
// Where what stands before an item in the list ends: the element before the
|
|
76608
76661
|
// item, or before its group when it is the first of it — or the start of the
|
|
76609
76662
|
// list's content when nothing does. Null when the element before is not laid
|
|
76610
|
-
// out.
|
|
76663
|
+
// out. It is where that element ends in the flow: where the item (or its
|
|
76664
|
+
// group) starts, less the gap and the margins between the two. Its own box is
|
|
76665
|
+
// where it is painted, which a sticky header stuck to the top of the screen
|
|
76666
|
+
// puts thousands of pixels away from its place in the list.
|
|
76611
76667
|
const readEndBefore = (itemEl, listEl, horizontal) => {
|
|
76612
76668
|
let slotEl = itemEl;
|
|
76613
76669
|
const groupListEl = itemEl.parentElement;
|
|
@@ -76616,11 +76672,20 @@ const readEndBefore = (itemEl, listEl, horizontal) => {
|
|
|
76616
76672
|
}
|
|
76617
76673
|
const beforeEl = slotEl.previousElementSibling;
|
|
76618
76674
|
if (beforeEl) {
|
|
76619
|
-
const
|
|
76620
|
-
if (
|
|
76675
|
+
const beforeRect = beforeEl.getBoundingClientRect();
|
|
76676
|
+
if (beforeRect.width === 0 && beforeRect.height === 0) {
|
|
76621
76677
|
return null;
|
|
76622
76678
|
}
|
|
76623
|
-
|
|
76679
|
+
const slotRect = slotEl.getBoundingClientRect();
|
|
76680
|
+
const containerStyle = window.getComputedStyle(slotEl.parentElement);
|
|
76681
|
+
const slotStyle = window.getComputedStyle(slotEl);
|
|
76682
|
+
const beforeStyle = window.getComputedStyle(beforeEl);
|
|
76683
|
+
// A gap the browser computes as "normal" is none in a flex or grid list.
|
|
76684
|
+
const lengthOf = value => parseFloat(value) || 0;
|
|
76685
|
+
if (horizontal) {
|
|
76686
|
+
return slotRect.left - lengthOf(containerStyle.columnGap) - lengthOf(slotStyle.marginLeft) - lengthOf(beforeStyle.marginRight);
|
|
76687
|
+
}
|
|
76688
|
+
return slotRect.top - lengthOf(containerStyle.rowGap) - lengthOf(slotStyle.marginTop) - lengthOf(beforeStyle.marginBottom);
|
|
76624
76689
|
}
|
|
76625
76690
|
if (slotEl.parentElement !== listEl) {
|
|
76626
76691
|
return null;
|
|
@@ -77947,10 +78012,12 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
77947
78012
|
* A collection read a slice at a time comes from `itemsAction(range)`: the run
|
|
77948
78013
|
* asks for what it is about to draw and keeps what it gets. The range says the
|
|
77949
78014
|
* same thing three ways, so a source can read it however it paginates —
|
|
77950
|
-
* `{ start, end }` (places in the collection,
|
|
77951
|
-
*
|
|
77952
|
-
*
|
|
77953
|
-
*
|
|
78015
|
+
* `{ start, end }` (places in the collection, `end` included as in
|
|
78016
|
+
* `Range: items=0-99`; a negative `start` counting back from the end like
|
|
78017
|
+
* `Range: items=-25`, which is what a list opening on its last items asks for
|
|
78018
|
+
* before it knows how many there are), `limit` (how many items:
|
|
78019
|
+
* `end - start + 1` — an API whose end is excluded is called with
|
|
78020
|
+
* `start + limit`), and
|
|
77954
78021
|
* `before`/`after`/`around` (the id of an item to count from, for a source
|
|
77955
78022
|
* paginating by cursor). Answer with the items (an array — that is all of
|
|
77956
78023
|
* them), or with a range the way a Content-Range does: `{ items, start, count }`
|
|
@@ -77965,10 +78032,11 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
77965
78032
|
* (see docs/resource.md).
|
|
77966
78033
|
*
|
|
77967
78034
|
* A collection that changes as a whole (a search reordering it) is a different
|
|
77968
|
-
* collection
|
|
77969
|
-
*
|
|
77970
|
-
*
|
|
77971
|
-
*
|
|
78035
|
+
* collection, and the run draws it from its first item: with `items`, another
|
|
78036
|
+
* array is another collection; with `itemsAction`, another reader is — a
|
|
78037
|
+
* reader bound to other params (`bindParams({ q })` with another `q`). A param
|
|
78038
|
+
* held in a signal changes what the reader reads without changing the reader:
|
|
78039
|
+
* give the run a `key` that follows it.
|
|
77972
78040
|
*
|
|
77973
78041
|
* An item says what it is where it is drawn: `renderItem` returns a
|
|
77974
78042
|
* `<List.Item>` carrying its own props (`selectable`, `value`, `selected`…),
|
|
@@ -77983,6 +78051,7 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
77983
78051
|
* findText?: (item: any, index: number) => string,
|
|
77984
78052
|
* items?: any[],
|
|
77985
78053
|
* itemsAction?: (range: {start: number, end: number, limit: number, before?: string, after?: string, around?: string, count?: number, signal: AbortSignal}) => any,
|
|
78054
|
+
* debounce?: number,
|
|
77986
78055
|
* count?: number,
|
|
77987
78056
|
* groupBy?: (item: any, index: number) => any,
|
|
77988
78057
|
* renderGroupLabel?: (item: any, index: number) => import("ignore:preact").ComponentChildren,
|
|
@@ -77993,6 +78062,7 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
77993
78062
|
* itemSize?: (index: number) => number,
|
|
77994
78063
|
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("ignore:preact").ComponentChildren,
|
|
77995
78064
|
* onRequestStateChange?: (state: {busy: boolean, refreshing: boolean, range: {start: number, end: number}|null}) => void,
|
|
78065
|
+
* onCountChange?: (count: number|undefined) => void,
|
|
77996
78066
|
* }>}
|
|
77997
78067
|
* @param {(item: any, index: number, state: {refreshing: boolean}) => any} props.renderItem
|
|
77998
78068
|
* What is drawn for one item, given its data and where it sits — its place in the list,
|
|
@@ -78017,6 +78087,12 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
78017
78087
|
* @param {(range: object) => any} [props.itemsAction]
|
|
78018
78088
|
* Where the items come from when the collection is read a slice at a time:
|
|
78019
78089
|
* a resource's range reader (`RESOURCE.GET_RANGE.bindParams(...)`).
|
|
78090
|
+
* @param {number} [props.debounce]
|
|
78091
|
+
* Milliseconds the reader must hold still before the run draws the
|
|
78092
|
+
* collection it reads: params following a field as the user types
|
|
78093
|
+
* (`bindParams({ q })`) ask once typing stops, not once per key. Meanwhile
|
|
78094
|
+
* the run keeps drawing the collection from before, `refreshing`. The
|
|
78095
|
+
* first collection is drawn at once.
|
|
78020
78096
|
* @param {(item: any, index: number) => any} [props.groupBy]
|
|
78021
78097
|
* What tells items that belong together apart from the others — the day of a
|
|
78022
78098
|
* message, the month of a game. Consecutive items sharing it are wrapped in a
|
|
@@ -78080,24 +78156,101 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
78080
78156
|
* collection's own ranks as `itemsAction` sees them, `null` once nothing is.
|
|
78081
78157
|
* A range called off and asked again right away stays one `busy`, and a list
|
|
78082
78158
|
* unmounted while asking says `busy: false` on its way out.
|
|
78159
|
+
* @param {(count: number|undefined) => void} [props.onCountChange]
|
|
78160
|
+
* Called with how many items the collection holds when that changes — for
|
|
78161
|
+
* the screen around the list to say it ("5 of 83 books"): the `count` the
|
|
78162
|
+
* source answers with each slice, or the one given to the run.
|
|
78163
|
+
* `undefined` until it is known; a run drawing another collection says it
|
|
78164
|
+
* again, so the number always belongs to the collection on screen.
|
|
78083
78165
|
*/
|
|
78084
78166
|
const ListItems = ({
|
|
78167
|
+
itemsAction,
|
|
78168
|
+
debounce,
|
|
78169
|
+
...props
|
|
78170
|
+
}) => {
|
|
78171
|
+
// Read so that this component renders in the pass of the List moving its
|
|
78172
|
+
// window, and the run with it: the List frames its window on the items its
|
|
78173
|
+
// runs drew in that same commit (see settleWindow). Skipped as an unchanged
|
|
78174
|
+
// child, it would leave the run to a render of its own, after that commit.
|
|
78175
|
+
useContext(RenderWindowContext);
|
|
78176
|
+
const {
|
|
78177
|
+
reader,
|
|
78178
|
+
settling
|
|
78179
|
+
} = useSettledReader(itemsAction, debounce);
|
|
78180
|
+
return jsx(ListItemsRun
|
|
78181
|
+
// Another reader is another collection — bindParams gives back the
|
|
78182
|
+
// reader it made for equal params — and the run holds the ranks, the
|
|
78183
|
+
// count and the asks of the one it draws.
|
|
78184
|
+
, {
|
|
78185
|
+
itemsAction: reader,
|
|
78186
|
+
settling: settling,
|
|
78187
|
+
...props
|
|
78188
|
+
}, reader && reader.isRangeReader ? readerKeyOf(reader) : undefined);
|
|
78189
|
+
};
|
|
78190
|
+
const ListItemsRun = ({
|
|
78085
78191
|
items,
|
|
78086
78192
|
itemsAction,
|
|
78193
|
+
settling,
|
|
78087
78194
|
count,
|
|
78088
78195
|
memoryBudget,
|
|
78089
78196
|
onRequestStateChange,
|
|
78197
|
+
onCountChange,
|
|
78090
78198
|
...runProps
|
|
78091
78199
|
}) => {
|
|
78092
78200
|
const store = useItemStore({
|
|
78093
78201
|
items,
|
|
78094
78202
|
count,
|
|
78095
78203
|
itemsAction,
|
|
78204
|
+
settling,
|
|
78096
78205
|
memoryBudget,
|
|
78097
|
-
onRequestStateChange
|
|
78206
|
+
onRequestStateChange,
|
|
78207
|
+
onCountChange
|
|
78098
78208
|
});
|
|
78099
78209
|
return useRunItems(store, runProps);
|
|
78100
78210
|
};
|
|
78211
|
+
const readerKeyByReader = new WeakMap();
|
|
78212
|
+
let readerKeyPrevious = 0;
|
|
78213
|
+
const readerKeyOf = reader => {
|
|
78214
|
+
let key = readerKeyByReader.get(reader);
|
|
78215
|
+
if (key === undefined) {
|
|
78216
|
+
readerKeyPrevious++;
|
|
78217
|
+
key = readerKeyPrevious;
|
|
78218
|
+
readerKeyByReader.set(reader, key);
|
|
78219
|
+
}
|
|
78220
|
+
return key;
|
|
78221
|
+
};
|
|
78222
|
+
// The reader the run draws: the one it is given, or under `debounce` the one
|
|
78223
|
+
// it was drawing until the one given has held still that long. Only between
|
|
78224
|
+
// two range readers — the first collection is drawn at once, and a collection
|
|
78225
|
+
// held in memory costs no request to follow.
|
|
78226
|
+
const useSettledReader = (itemsAction, debounce) => {
|
|
78227
|
+
const settledRef = useRef(itemsAction);
|
|
78228
|
+
const [, setSettleCount] = useState(0);
|
|
78229
|
+
const settled = settledRef.current;
|
|
78230
|
+
const waits = Boolean(debounce) && itemsAction !== settled && Boolean(itemsAction && itemsAction.isRangeReader) && Boolean(settled && settled.isRangeReader);
|
|
78231
|
+
if (!waits) {
|
|
78232
|
+
settledRef.current = itemsAction;
|
|
78233
|
+
}
|
|
78234
|
+
useLayoutEffect(() => {
|
|
78235
|
+
if (!waits) {
|
|
78236
|
+
return undefined;
|
|
78237
|
+
}
|
|
78238
|
+
const timeout = setTimeout(() => {
|
|
78239
|
+
settledRef.current = itemsAction;
|
|
78240
|
+
setSettleCount(settleCount => settleCount + 1);
|
|
78241
|
+
}, debounce);
|
|
78242
|
+
return () => {
|
|
78243
|
+
clearTimeout(timeout);
|
|
78244
|
+
};
|
|
78245
|
+
}, [waits, itemsAction, debounce]);
|
|
78246
|
+
return waits ? {
|
|
78247
|
+
reader: settled,
|
|
78248
|
+
settling: true
|
|
78249
|
+
} : {
|
|
78250
|
+
reader: itemsAction,
|
|
78251
|
+
settling: false
|
|
78252
|
+
};
|
|
78253
|
+
};
|
|
78101
78254
|
|
|
78102
78255
|
// The items a `loading` list is told to expect (loadingSkeletonCount): a run
|
|
78103
78256
|
// holding none of them, and asking for none — they arrive through the app.
|
|
@@ -78178,10 +78331,13 @@ const useRunItems = (store, {
|
|
|
78178
78331
|
skeletonItem = {};
|
|
78179
78332
|
const virtualItemSize = listItems.virtualItemSizeSignal.value;
|
|
78180
78333
|
if (virtualItemSize) {
|
|
78334
|
+
// The room is the item's and what stands before it (see
|
|
78335
|
+
// readSpaceBetweenItems): the box takes what is left of it.
|
|
78336
|
+
const minSize = `calc(${virtualItemSize}px - var(--x-list-item-between, 0px))`;
|
|
78181
78337
|
if (listItems.horizontal) {
|
|
78182
|
-
skeletonItem.itemMinWidth =
|
|
78338
|
+
skeletonItem.itemMinWidth = minSize;
|
|
78183
78339
|
} else {
|
|
78184
|
-
skeletonItem.itemMinHeight =
|
|
78340
|
+
skeletonItem.itemMinHeight = minSize;
|
|
78185
78341
|
}
|
|
78186
78342
|
}
|
|
78187
78343
|
return skeletonItem;
|
|
@@ -78715,6 +78871,7 @@ const ListItemsFailure = ({
|
|
|
78715
78871
|
// hits the network again.
|
|
78716
78872
|
const ITEM_STORE_MAX_DEFAULT = 1000;
|
|
78717
78873
|
const ITEM_STORE_KEEP_AROUND = 250;
|
|
78874
|
+
const COUNT_NOT_SAID = {};
|
|
78718
78875
|
const rangeIsSame = (a, b) => {
|
|
78719
78876
|
if (!a || !b) {
|
|
78720
78877
|
return a === b;
|
|
@@ -78731,8 +78888,10 @@ const useItemStore = ({
|
|
|
78731
78888
|
items,
|
|
78732
78889
|
count,
|
|
78733
78890
|
itemsAction,
|
|
78891
|
+
settling,
|
|
78734
78892
|
memoryBudget,
|
|
78735
|
-
onRequestStateChange
|
|
78893
|
+
onRequestStateChange,
|
|
78894
|
+
onCountChange
|
|
78736
78895
|
}) => {
|
|
78737
78896
|
// A collection given whole (`items`) is held from the first render: nothing
|
|
78738
78897
|
// to ask for, nothing to keep across mounts, nothing to invalidate. The rest
|
|
@@ -78815,6 +78974,68 @@ const useItemStore = ({
|
|
|
78815
78974
|
if (staleRanksRef.current === null) {
|
|
78816
78975
|
staleRanksRef.current = restored ? new Set(pages.byIndex.keys()) : new Set();
|
|
78817
78976
|
}
|
|
78977
|
+
const takeInPage = ({
|
|
78978
|
+
pageItems,
|
|
78979
|
+
pageStart,
|
|
78980
|
+
pageCount
|
|
78981
|
+
}) => {
|
|
78982
|
+
// An item from before is gone from where it stood when the page puts
|
|
78983
|
+
// it at another rank (it would be drawn twice), or when its rank is
|
|
78984
|
+
// past the end of the collection.
|
|
78985
|
+
const staleRanks = staleRanksRef.current;
|
|
78986
|
+
const pageEnd = pageStart + pageItems.length;
|
|
78987
|
+
let dropped = false;
|
|
78988
|
+
if (staleRanks.size > 0) {
|
|
78989
|
+
const pageIds = new Set();
|
|
78990
|
+
for (const pageItem of pageItems) {
|
|
78991
|
+
if (pageItem && pageItem.id !== undefined) {
|
|
78992
|
+
pageIds.add(pageItem.id);
|
|
78993
|
+
}
|
|
78994
|
+
}
|
|
78995
|
+
for (const rank of staleRanks) {
|
|
78996
|
+
if (rank >= pageStart && rank < pageEnd) {
|
|
78997
|
+
continue;
|
|
78998
|
+
}
|
|
78999
|
+
const staleItem = pages.byIndex.get(rank);
|
|
79000
|
+
if (rank >= pageCount || staleItem && pageIds.has(staleItem.id)) {
|
|
79001
|
+
staleRanks.delete(rank);
|
|
79002
|
+
pages.byIndex.delete(rank);
|
|
79003
|
+
dropped = true;
|
|
79004
|
+
}
|
|
79005
|
+
}
|
|
79006
|
+
}
|
|
79007
|
+
let i = 0;
|
|
79008
|
+
while (i < pageItems.length) {
|
|
79009
|
+
pages.byIndex.set(pageStart + i, pageItems[i]);
|
|
79010
|
+
staleRanks.delete(pageStart + i);
|
|
79011
|
+
i++;
|
|
79012
|
+
}
|
|
79013
|
+
pages.count = pageCount;
|
|
79014
|
+
// Which rank holds which id, kept by the source so a list drawing
|
|
79015
|
+
// this collection again finds it drawn (see readComposition above).
|
|
79016
|
+
if (itemsAction.writeComposition) {
|
|
79017
|
+
itemsAction.writeComposition({
|
|
79018
|
+
byIndex: pages.byIndex,
|
|
79019
|
+
count: pageCount,
|
|
79020
|
+
replace: dropped
|
|
79021
|
+
});
|
|
79022
|
+
}
|
|
79023
|
+
};
|
|
79024
|
+
// The pages answered since the last render, taken in by the render and not
|
|
79025
|
+
// as they land: what the list reads of the run — how many items it stands for
|
|
79026
|
+
// (see listItems.take), where an item sits (its locator) — then moves in one
|
|
79027
|
+
// step. Taken in on landing, an item could be located past the count the list
|
|
79028
|
+
// still holds, and a list held on it would aim its window there at every
|
|
79029
|
+
// render, framed back each time.
|
|
79030
|
+
const pagesLandedRef = useRef([]);
|
|
79031
|
+
const pagesLanded = pagesLandedRef.current;
|
|
79032
|
+
const pagesTakenIn = pagesLanded.length > 0;
|
|
79033
|
+
if (pagesTakenIn) {
|
|
79034
|
+
pagesLandedRef.current = [];
|
|
79035
|
+
for (const page of pagesLanded) {
|
|
79036
|
+
takeInPage(page);
|
|
79037
|
+
}
|
|
79038
|
+
}
|
|
78818
79039
|
const [refreshing, setRefreshing] = useState(false);
|
|
78819
79040
|
// A source that says when what it reads has moved (a resource range reader
|
|
78820
79041
|
// does: see rerunOn.GET_RANGE) is heard here — a write deciding who belongs
|
|
@@ -78845,28 +79066,55 @@ const useItemStore = ({
|
|
|
78845
79066
|
const [failure, setFailure] = useState(null);
|
|
78846
79067
|
const listItems = useContext(ListItemsContext);
|
|
78847
79068
|
// The items are there, which is what the list waits for to place itself on the
|
|
78848
|
-
// item it is held at (see placeWhereHeld). Said from an effect
|
|
78849
|
-
// during this very render must not be written
|
|
79069
|
+
// item it is held at (see placeWhereHeld). Said from an effect, once the run
|
|
79070
|
+
// has drawn them: a signal read during this very render must not be written
|
|
79071
|
+
// during it.
|
|
78850
79072
|
useLayoutEffect(() => {
|
|
78851
|
-
if (
|
|
79073
|
+
if (inMemory) {
|
|
79074
|
+
if (itemsHeldRef.current) {
|
|
79075
|
+
return;
|
|
79076
|
+
}
|
|
79077
|
+
itemsHeldRef.current = true;
|
|
79078
|
+
} else if (!pagesTakenIn) {
|
|
78852
79079
|
return;
|
|
78853
79080
|
}
|
|
78854
|
-
itemsHeldRef.current = true;
|
|
78855
79081
|
listItems.pagesSignal.value = listItems.pagesSignal.peek() + 1;
|
|
78856
79082
|
});
|
|
78857
79083
|
// Before the first answer a run does not know how many items it stands for.
|
|
78858
79084
|
// It stands for a page of them: a list that is about to be filled looks
|
|
78859
79085
|
// like items on their way, not like an empty list.
|
|
78860
79086
|
const itemCount = pages.count ?? count ?? listItems.pageSize;
|
|
79087
|
+
// The items drawn are from before while they are read again, and while the
|
|
79088
|
+
// collection that replaces them waits for its params to hold still (see
|
|
79089
|
+
// useSettledReader).
|
|
79090
|
+
const refreshingShown = refreshing || Boolean(settling);
|
|
78861
79091
|
useLayoutEffect(() => {
|
|
78862
|
-
if (!
|
|
79092
|
+
if (!refreshingShown) {
|
|
78863
79093
|
return null;
|
|
78864
79094
|
}
|
|
78865
79095
|
listItems.refreshingSignal.value = listItems.refreshingSignal.peek() + 1;
|
|
78866
79096
|
return () => {
|
|
78867
79097
|
listItems.refreshingSignal.value = listItems.refreshingSignal.peek() - 1;
|
|
78868
79098
|
};
|
|
78869
|
-
}, [
|
|
79099
|
+
}, [refreshingShown]);
|
|
79100
|
+
|
|
79101
|
+
// How many items the collection holds, for the screen around the list to
|
|
79102
|
+
// say: the count the source answered or the caller gave, never the page the
|
|
79103
|
+
// run stands for before it knows. Said by each run on its first commit, so a
|
|
79104
|
+
// run drawing another collection takes back what the one before said.
|
|
79105
|
+
const countKnown = pages.count ?? count;
|
|
79106
|
+
const onCountChangeRef = useRef(onCountChange);
|
|
79107
|
+
onCountChangeRef.current = onCountChange;
|
|
79108
|
+
const countSaidRef = useRef(COUNT_NOT_SAID);
|
|
79109
|
+
useLayoutEffect(() => {
|
|
79110
|
+
if (countSaidRef.current === countKnown) {
|
|
79111
|
+
return;
|
|
79112
|
+
}
|
|
79113
|
+
countSaidRef.current = countKnown;
|
|
79114
|
+
if (onCountChangeRef.current) {
|
|
79115
|
+
onCountChangeRef.current(countKnown);
|
|
79116
|
+
}
|
|
79117
|
+
});
|
|
78870
79118
|
|
|
78871
79119
|
// What the run is doing, for the screen around the list to draw: the list has
|
|
78872
79120
|
// its own skeletons, what is around it has to be told. Read from the request
|
|
@@ -78901,8 +79149,11 @@ const useItemStore = ({
|
|
|
78901
79149
|
};
|
|
78902
79150
|
// A list taken off the screen while it was asking leaves nothing ringing
|
|
78903
79151
|
// behind it: whoever is drawing "looking for items" has to stop.
|
|
79152
|
+
const mountedRef = useRef(false);
|
|
78904
79153
|
useLayoutEffect(() => {
|
|
79154
|
+
mountedRef.current = true;
|
|
78905
79155
|
return () => {
|
|
79156
|
+
mountedRef.current = false;
|
|
78906
79157
|
if (requestStateRef.current.busy && onRequestStateChangeRef.current) {
|
|
78907
79158
|
onRequestStateChangeRef.current({
|
|
78908
79159
|
busy: false,
|
|
@@ -78915,7 +79166,7 @@ const useItemStore = ({
|
|
|
78915
79166
|
const store = {
|
|
78916
79167
|
itemCount,
|
|
78917
79168
|
failure,
|
|
78918
|
-
refreshing,
|
|
79169
|
+
refreshing: refreshingShown,
|
|
78919
79170
|
// JS memory is cheap next to the DOM, but a long enough scroll accumulates
|
|
78920
79171
|
// everything it ever went through. Items far from what is on screen are
|
|
78921
79172
|
// dropped and simply asked for again if the user goes back — the same
|
|
@@ -79164,51 +79415,21 @@ const useItemStore = ({
|
|
|
79164
79415
|
const pageItems = Array.isArray(page) ? page : page.items;
|
|
79165
79416
|
const pageStart = Array.isArray(page) ? 0 : page.start ?? 0;
|
|
79166
79417
|
const pageCount = Array.isArray(page) ? pageItems.length : page.count ?? pageStart + pageItems.length;
|
|
79418
|
+
const pageLanded = {
|
|
79419
|
+
pageItems,
|
|
79420
|
+
pageStart,
|
|
79421
|
+
pageCount
|
|
79422
|
+
};
|
|
79423
|
+
if (!mountedRef.current) {
|
|
79424
|
+
// No render is coming to take it in, and the source still keeps
|
|
79425
|
+
// what came back for the next list drawing this collection.
|
|
79426
|
+
takeInPage(pageLanded);
|
|
79427
|
+
return;
|
|
79428
|
+
}
|
|
79167
79429
|
// Before the items land: what is on screen has to stay where it is,
|
|
79168
79430
|
// and the DOM still shows the state to hold onto.
|
|
79169
79431
|
listItems.captureAnchor();
|
|
79170
|
-
|
|
79171
|
-
// it at another rank (it would be drawn twice), or when its rank is
|
|
79172
|
-
// past the end of the collection.
|
|
79173
|
-
const staleRanksNow = staleRanksRef.current;
|
|
79174
|
-
const pageEnd = pageStart + pageItems.length;
|
|
79175
|
-
let dropped = false;
|
|
79176
|
-
if (staleRanksNow.size > 0) {
|
|
79177
|
-
const pageIds = new Set();
|
|
79178
|
-
for (const pageItem of pageItems) {
|
|
79179
|
-
if (pageItem && pageItem.id !== undefined) {
|
|
79180
|
-
pageIds.add(pageItem.id);
|
|
79181
|
-
}
|
|
79182
|
-
}
|
|
79183
|
-
for (const rank of staleRanksNow) {
|
|
79184
|
-
if (rank >= pageStart && rank < pageEnd) {
|
|
79185
|
-
continue;
|
|
79186
|
-
}
|
|
79187
|
-
const staleItem = pages.byIndex.get(rank);
|
|
79188
|
-
if (rank >= pageCount || staleItem && pageIds.has(staleItem.id)) {
|
|
79189
|
-
staleRanksNow.delete(rank);
|
|
79190
|
-
pages.byIndex.delete(rank);
|
|
79191
|
-
dropped = true;
|
|
79192
|
-
}
|
|
79193
|
-
}
|
|
79194
|
-
}
|
|
79195
|
-
let i = 0;
|
|
79196
|
-
while (i < pageItems.length) {
|
|
79197
|
-
pages.byIndex.set(pageStart + i, pageItems[i]);
|
|
79198
|
-
staleRanksNow.delete(pageStart + i);
|
|
79199
|
-
i++;
|
|
79200
|
-
}
|
|
79201
|
-
pages.count = pageCount;
|
|
79202
|
-
// Which rank holds which id, kept by the source so a list drawing
|
|
79203
|
-
// this collection again finds it drawn (see readComposition above).
|
|
79204
|
-
if (itemsAction.writeComposition) {
|
|
79205
|
-
itemsAction.writeComposition({
|
|
79206
|
-
byIndex: pages.byIndex,
|
|
79207
|
-
count: pageCount,
|
|
79208
|
-
replace: dropped
|
|
79209
|
-
});
|
|
79210
|
-
}
|
|
79211
|
-
listItems.pagesSignal.value = listItems.pagesSignal.peek() + 1;
|
|
79432
|
+
pagesLandedRef.current.push(pageLanded);
|
|
79212
79433
|
setPageVersion(version => version + 1);
|
|
79213
79434
|
};
|
|
79214
79435
|
const failed = error => {
|
|
@@ -79572,8 +79793,9 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
79572
79793
|
* @param {number} [props.virtualItemSize]
|
|
79573
79794
|
* The room an item never drawn is held at, in px along the scroll axis: what
|
|
79574
79795
|
* the fillers hold for it outside the render window, what a scroll position
|
|
79575
|
-
* inside them is read with, and the least an item on its way takes
|
|
79576
|
-
* `renderSkeleton`)
|
|
79796
|
+
* inside them is read with, and the least room an item on its way takes
|
|
79797
|
+
* (see `renderSkeleton`) — a room counts what stands before the item, a
|
|
79798
|
+
* gap or a separator, so the item's own box takes the rest of it. An item the list has drawn is held at the room it took
|
|
79577
79799
|
* then, and an item whose run says its room (`<List.Items itemSize>`) at
|
|
79578
79800
|
* that room. Left out, it is the average of the items measured so far — once when
|
|
79579
79801
|
* the list mounts, again when a popup around it opens, and after each commit
|