@jsenv/navi 0.29.407 → 0.29.409
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 +199 -47
- package/dist/dev/jsenv_navi.js.map +10 -8
- package/dist/jsenv_navi.js +199 -47
- package/dist/jsenv_navi.js.map +10 -8
- package/docs/AI_INSTRUCTIONS.md +3 -2
- package/docs/scroll.md +52 -1
- package/package.json +2 -2
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -13122,7 +13122,8 @@ const useActionBoundToOneParam = (action, paramsSignal) => {
|
|
|
13122
13122
|
}, []);
|
|
13123
13123
|
return [boundAction, getValue(), setValue];
|
|
13124
13124
|
};
|
|
13125
|
-
//
|
|
13125
|
+
// The action bound to the signal, without reading it: the caller is not
|
|
13126
|
+
// re-rendered by the params (a control, a <details>).
|
|
13126
13127
|
const useAction = (action, paramsSignal) => {
|
|
13127
13128
|
return useBoundAction(action, paramsSignal);
|
|
13128
13129
|
};
|
|
@@ -13144,12 +13145,13 @@ const useBoundAction = (action, actionParamsSignal) => {
|
|
|
13144
13145
|
if (existingNoopAction) {
|
|
13145
13146
|
return existingNoopAction;
|
|
13146
13147
|
}
|
|
13148
|
+
// Not bound to the params signal: nothing reads what a noop runs with, and
|
|
13149
|
+
// a proxy following the signal would retarget (a new child action for each
|
|
13150
|
+
// new value) at every change — for every button of a form, at every change
|
|
13151
|
+
// of any of its fields.
|
|
13147
13152
|
const noopAction = createAction(() => {}, { params: undefined });
|
|
13148
|
-
|
|
13149
|
-
|
|
13150
|
-
: noopAction;
|
|
13151
|
-
noopActionRef.current = noopActionBound;
|
|
13152
|
-
return noopActionBound;
|
|
13153
|
+
noopActionRef.current = noopAction;
|
|
13154
|
+
return noopAction;
|
|
13153
13155
|
}
|
|
13154
13156
|
const isFunction = typeof action === "function";
|
|
13155
13157
|
if (!isFunction) {
|
|
@@ -23009,7 +23011,7 @@ import.meta.css = /* css */[`[data-scrollable] > .navi_loading_outline_wrapper,
|
|
|
23009
23011
|
}
|
|
23010
23012
|
}
|
|
23011
23013
|
|
|
23012
|
-
[hidden] {
|
|
23014
|
+
[hidden]:not([hidden="until-found" i]) {
|
|
23013
23015
|
display: none !important;
|
|
23014
23016
|
}
|
|
23015
23017
|
`, "@jsenv/navi/src/box/box.jsx"];
|
|
@@ -42656,7 +42658,14 @@ const useControlProps = (props, {
|
|
|
42656
42658
|
persists,
|
|
42657
42659
|
uiActionInternal
|
|
42658
42660
|
});
|
|
42659
|
-
|
|
42661
|
+
// What the control draws is what it holds by itself, and this read is what
|
|
42662
|
+
// re-renders it when that changes. A button inheriting the value of the
|
|
42663
|
+
// control around it (a submit in a form, see ownUIStateSignal) draws none of
|
|
42664
|
+
// that value, it only hands it to its action: subscribed to it, every button
|
|
42665
|
+
// of a form would re-render at each change of any of its fields.
|
|
42666
|
+
// eslint-disable-next-line no-unused-expressions
|
|
42667
|
+
uiStateController.ownUIStateSignal.value;
|
|
42668
|
+
const boundAction = useAction(props.action, uiStateController.uiStateSignal);
|
|
42660
42669
|
const [controlRootProps, controlHostProps] = useInteractiveProps(props, {
|
|
42661
42670
|
uiStateController,
|
|
42662
42671
|
boundAction,
|
|
@@ -43423,7 +43432,10 @@ const useControlProps = (props, {
|
|
|
43423
43432
|
};
|
|
43424
43433
|
}
|
|
43425
43434
|
}
|
|
43426
|
-
|
|
43435
|
+
|
|
43436
|
+
// Its own, like syncDomState: a button's DOM value is not the value it
|
|
43437
|
+
// inherits from the control around it.
|
|
43438
|
+
const uiState = uiStateController.ownUIStateSignal.peek();
|
|
43427
43439
|
const domProps = toDomProps(uiState);
|
|
43428
43440
|
{
|
|
43429
43441
|
// Same as syncDomState: a field being typed into keeps its own text, so a
|
|
@@ -43972,7 +43984,9 @@ const useInteractiveProps = (props, {
|
|
|
43972
43984
|
// stop waiting on a run that is still going. What it waits on is the run
|
|
43973
43985
|
// it started, from navi_action_start to its settlement.
|
|
43974
43986
|
const runningAction = uiStateController.runningActionSignal.value;
|
|
43975
|
-
|
|
43987
|
+
// Only whether it runs: the whole status (useActionStatus) reads the params
|
|
43988
|
+
// too, which for a button inheriting its form's value are the form.
|
|
43989
|
+
const actionRunning = (runningAction || boundAction).runningStateSignal.value === RUNNING;
|
|
43976
43990
|
const networkPolicyReason = useNetworkPolicyReason();
|
|
43977
43991
|
const {
|
|
43978
43992
|
disabled,
|
|
@@ -44020,7 +44034,7 @@ const useInteractiveProps = (props, {
|
|
|
44020
44034
|
// just set stays visible and interactive while the action runs — no
|
|
44021
44035
|
// loading, no readonly. On failure resetOnError rolls the state back and
|
|
44022
44036
|
// the error callout says why.
|
|
44023
|
-
const actionLoading = optimistic ? false :
|
|
44037
|
+
const actionLoading = optimistic ? false : actionRunning;
|
|
44024
44038
|
const loadingResolved = loadingBase || actionLoading;
|
|
44025
44039
|
const readOnlyResolved = readOnlyBase || actionLoading;
|
|
44026
44040
|
// Read-only, and what this control opens still opens: reading what is in
|
|
@@ -44215,7 +44229,11 @@ const useInteractiveProps = (props, {
|
|
|
44215
44229
|
errorEffect: actionErrorEffect,
|
|
44216
44230
|
errorMapping
|
|
44217
44231
|
});
|
|
44218
|
-
|
|
44232
|
+
// What the attribute says is that an action is there (see
|
|
44233
|
+
// findClosestControlWithAction); its text is a label. Peeked (toString),
|
|
44234
|
+
// because `.callSource` follows the params — for a button inheriting its
|
|
44235
|
+
// form's value, a re-render at every change of any field.
|
|
44236
|
+
const dataAction = action === undefined ? undefined : String(boundAction);
|
|
44219
44237
|
Object.assign(controlHostProps, {
|
|
44220
44238
|
"data-action": dataAction
|
|
44221
44239
|
});
|
|
@@ -53143,30 +53161,9 @@ const ButtonUI = props => {
|
|
|
53143
53161
|
} = buttonControlHostProps;
|
|
53144
53162
|
const loading = basePseudoState[":-navi-loading"];
|
|
53145
53163
|
|
|
53146
|
-
//
|
|
53147
|
-
|
|
53148
|
-
const
|
|
53149
|
-
let as = "button";
|
|
53150
|
-
let innerTarget;
|
|
53151
|
-
let innerRel;
|
|
53152
|
-
let innerCurrent;
|
|
53153
|
-
if (isLink) {
|
|
53154
|
-
as = "a";
|
|
53155
|
-
const {
|
|
53156
|
-
isSameSite,
|
|
53157
|
-
isCurrent
|
|
53158
|
-
} = getHrefTargetInfo(href);
|
|
53159
|
-
innerTarget = target === undefined ? isSameSite ? undefined : "_blank" : target;
|
|
53160
|
-
innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
53161
|
-
innerCurrent = isCurrent;
|
|
53162
|
-
}
|
|
53163
|
-
// For a button that has only an href: nothing else knows it points at the
|
|
53164
|
-
// page one is on. A route says so through `pseudoState`, which Box lays over
|
|
53165
|
-
// this one.
|
|
53166
|
-
buttonControlHostProps.basePseudoState = {
|
|
53167
|
-
...basePseudoState,
|
|
53168
|
-
":-navi-href-current": innerCurrent
|
|
53169
|
-
};
|
|
53164
|
+
// Where the href leads next to the page one is on (the target, the rel, the
|
|
53165
|
+
// current state) is resolved above, by ButtonHrefResolver.
|
|
53166
|
+
const as = href === undefined ? "button" : "a";
|
|
53170
53167
|
|
|
53171
53168
|
// Worn as an attribute, like a link's (see link_replace.js): read off the
|
|
53172
53169
|
// anchor by the click handler, off the source by --navi-nav-to.
|
|
@@ -53215,8 +53212,8 @@ const ButtonUI = props => {
|
|
|
53215
53212
|
ref: ref,
|
|
53216
53213
|
as: as,
|
|
53217
53214
|
href: href,
|
|
53218
|
-
target:
|
|
53219
|
-
rel:
|
|
53215
|
+
target: target,
|
|
53216
|
+
rel: rel,
|
|
53220
53217
|
replace: undefined,
|
|
53221
53218
|
...replaceRequest,
|
|
53222
53219
|
document: undefined,
|
|
@@ -53516,6 +53513,49 @@ const ButtonConfirm = ({
|
|
|
53516
53513
|
});
|
|
53517
53514
|
};
|
|
53518
53515
|
|
|
53516
|
+
const ButtonHrefResolver = props => {
|
|
53517
|
+
const Next = useNextResolver();
|
|
53518
|
+
if (props.href !== undefined) {
|
|
53519
|
+
return renderResolver(ButtonWithHref, props);
|
|
53520
|
+
}
|
|
53521
|
+
return jsx(Next, {
|
|
53522
|
+
...props
|
|
53523
|
+
});
|
|
53524
|
+
};
|
|
53525
|
+
|
|
53526
|
+
// What the href means next to the page one is on depends on the document url,
|
|
53527
|
+
// so a button with an href re-renders at every address write. One without
|
|
53528
|
+
// never reaches here: it has nothing to re-compute, and every button on screen
|
|
53529
|
+
// would otherwise pay for each write.
|
|
53530
|
+
const ButtonWithHref = props => {
|
|
53531
|
+
const Next = useNextResolver();
|
|
53532
|
+
const {
|
|
53533
|
+
href,
|
|
53534
|
+
target,
|
|
53535
|
+
rel,
|
|
53536
|
+
pseudoState
|
|
53537
|
+
} = props;
|
|
53538
|
+
// Read for the subscription: getHrefTargetInfo reads window.location.
|
|
53539
|
+
useDocumentUrl();
|
|
53540
|
+
const {
|
|
53541
|
+
isSameSite,
|
|
53542
|
+
isCurrent
|
|
53543
|
+
} = getHrefTargetInfo(href);
|
|
53544
|
+
return jsx(Next, {
|
|
53545
|
+
...props,
|
|
53546
|
+
target: target === undefined ? isSameSite ? undefined : "_blank" : target,
|
|
53547
|
+
rel: rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel
|
|
53548
|
+
// Under whatever the caller already says: a route knows better whether
|
|
53549
|
+
// it is the page one is on (see button_route.jsx), and a demo can force
|
|
53550
|
+
// the state.
|
|
53551
|
+
,
|
|
53552
|
+
pseudoState: {
|
|
53553
|
+
":-navi-href-current": isCurrent,
|
|
53554
|
+
...pseudoState
|
|
53555
|
+
}
|
|
53556
|
+
});
|
|
53557
|
+
};
|
|
53558
|
+
|
|
53519
53559
|
const ButtonRouteResolver = props => {
|
|
53520
53560
|
const Next = useNextResolver();
|
|
53521
53561
|
if (props.route) {
|
|
@@ -53773,7 +53813,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
53773
53813
|
* (it wears it itself) and loses the shrink under the finger, which has
|
|
53774
53814
|
* nothing left to scale but the interactive area itself.
|
|
53775
53815
|
*/
|
|
53776
|
-
const Button = /*#__PURE__*/createComponentResolver([ButtonFirstResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonConfirmResolver, ButtonUI]);
|
|
53816
|
+
const Button = /*#__PURE__*/createComponentResolver([ButtonFirstResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonConfirmResolver, ButtonHrefResolver, ButtonUI]);
|
|
53777
53817
|
|
|
53778
53818
|
installImportMetaCssBuild(import.meta);
|
|
53779
53819
|
const css$O = /* css */`.navi_control_swap {
|
|
@@ -71269,12 +71309,28 @@ const css$x = /* css */`@layer navi {
|
|
|
71269
71309
|
display: inline-block;
|
|
71270
71310
|
}
|
|
71271
71311
|
|
|
71312
|
+
.navi_list_find_stand_in {
|
|
71313
|
+
height: calc(var(--x-find-line-count) * var(--x-find-line-size));
|
|
71314
|
+
color: #0000;
|
|
71315
|
+
line-height: var(--x-find-line-size);
|
|
71316
|
+
white-space: pre;
|
|
71317
|
+
overflow: clip;
|
|
71318
|
+
}
|
|
71319
|
+
|
|
71272
71320
|
.navi_list_container[data-horizontal] {
|
|
71273
71321
|
--list-max-height: none;
|
|
71274
71322
|
|
|
71275
71323
|
& > .navi_list_scroll_container > .navi_list > .navi_list_virtual_filler {
|
|
71276
71324
|
width: var(--size-to-fill, 0px);
|
|
71277
71325
|
height: 100%;
|
|
71326
|
+
display: flex;
|
|
71327
|
+
|
|
71328
|
+
& > .navi_list_find_stand_in {
|
|
71329
|
+
width: calc(var(--x-find-line-count) * var(--x-find-line-size));
|
|
71330
|
+
writing-mode: vertical-lr;
|
|
71331
|
+
flex-shrink: 0;
|
|
71332
|
+
height: auto;
|
|
71333
|
+
}
|
|
71278
71334
|
}
|
|
71279
71335
|
}
|
|
71280
71336
|
|
|
@@ -73871,10 +73927,12 @@ const Fallback = ({
|
|
|
73871
73927
|
// settled after the first commit.
|
|
73872
73928
|
const VirtualFiller = ({
|
|
73873
73929
|
edge,
|
|
73874
|
-
itemCount
|
|
73930
|
+
itemCount,
|
|
73931
|
+
findChunks
|
|
73875
73932
|
}) => {
|
|
73876
73933
|
const listRows = useContext(ListRowsContext);
|
|
73877
|
-
const
|
|
73934
|
+
const virtualItemSize = listRows.virtualItemSizeSignal.value;
|
|
73935
|
+
const sizeToFill = itemCount * virtualItemSize;
|
|
73878
73936
|
if (!sizeToFill) {
|
|
73879
73937
|
return null;
|
|
73880
73938
|
}
|
|
@@ -73885,8 +73943,17 @@ const VirtualFiller = ({
|
|
|
73885
73943
|
"navi-virtual-filler": edge,
|
|
73886
73944
|
"aria-hidden": true,
|
|
73887
73945
|
style: {
|
|
73888
|
-
"--size-to-fill": `${sizeToFill}px
|
|
73889
|
-
|
|
73946
|
+
"--size-to-fill": `${sizeToFill}px`,
|
|
73947
|
+
"--x-find-line-size": findChunks ? `${virtualItemSize}px` : undefined
|
|
73948
|
+
},
|
|
73949
|
+
children: findChunks && findChunks.map(chunk => jsx("div", {
|
|
73950
|
+
className: "navi_list_find_stand_in",
|
|
73951
|
+
hidden: "until-found",
|
|
73952
|
+
style: {
|
|
73953
|
+
"--x-find-line-count": chunk.to - chunk.from
|
|
73954
|
+
},
|
|
73955
|
+
children: chunk.text
|
|
73956
|
+
}, `${chunk.from}_${chunk.to}`))
|
|
73890
73957
|
});
|
|
73891
73958
|
};
|
|
73892
73959
|
|
|
@@ -74522,6 +74589,7 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
74522
74589
|
*
|
|
74523
74590
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
74524
74591
|
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("ignore:preact").ComponentChildren,
|
|
74592
|
+
* findText?: (item: any, index: number) => string,
|
|
74525
74593
|
* items?: any[],
|
|
74526
74594
|
* itemsAction?: (range: {start: number, end: number, limit: number, before?: string, after?: string, around?: string, count?: number, signal: AbortSignal}) => any,
|
|
74527
74595
|
* count?: number,
|
|
@@ -74540,6 +74608,13 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
74540
74608
|
* the run. `state.refreshing` says the rows drawn are the ones from before
|
|
74541
74609
|
* while the run reads the collection again — the list carries
|
|
74542
74610
|
* `navi-refreshing` for the same reason.
|
|
74611
|
+
* @param {(item: any, index: number) => string} [props.findText]
|
|
74612
|
+
* The text of a row as the browser's find in page (Cmd/Ctrl + F) sees it
|
|
74613
|
+
* while the row is not drawn — without it, find reaches only the rows in the
|
|
74614
|
+
* render window. The rows held off screen then carry that text where they
|
|
74615
|
+
* stand, hidden until the browser finds it there: it scrolls to the place,
|
|
74616
|
+
* and the list draws the row. One line per row. A row the run does not hold
|
|
74617
|
+
* yet has nothing to be found by.
|
|
74543
74618
|
* @param {any[]} [props.items]
|
|
74544
74619
|
* The collection, when it is held in memory: all of it, in order. Nothing is
|
|
74545
74620
|
* ever asked for — `itemsAction`, `count`, `pageSize` and `memoryBudget` have
|
|
@@ -74636,12 +74711,19 @@ const createStoreHoldingNothing = rowCount => {
|
|
|
74636
74711
|
};
|
|
74637
74712
|
};
|
|
74638
74713
|
|
|
74714
|
+
// How many rows share one element of the text a filler carries for find in
|
|
74715
|
+
// page (see findText). One element per row would put back the DOM nodes the
|
|
74716
|
+
// window saves; one per filler would be revealed whole — every row of it laid
|
|
74717
|
+
// out — by the first match found in it.
|
|
74718
|
+
const FIND_CHUNK_ROW_COUNT = 64;
|
|
74719
|
+
|
|
74639
74720
|
// What a run draws: the rows its window frames, a skeleton for each one the
|
|
74640
74721
|
// store does not hold, and fillers holding the room of the rows outside it.
|
|
74641
74722
|
// Which rows are held, and fetching the others, is the store's (see
|
|
74642
74723
|
// useItemStore).
|
|
74643
74724
|
const useRunRows = (store, {
|
|
74644
74725
|
renderItem,
|
|
74726
|
+
findText,
|
|
74645
74727
|
pageSize,
|
|
74646
74728
|
groupBy,
|
|
74647
74729
|
renderGroupLabel,
|
|
@@ -74849,6 +74931,37 @@ const useRunRows = (store, {
|
|
|
74849
74931
|
}
|
|
74850
74932
|
group.children.push(rowNode);
|
|
74851
74933
|
};
|
|
74934
|
+
// The text of the rows a filler stands for, cut in chunks aligned on the
|
|
74935
|
+
// run's own ranks: the window sliding changes the chunk at its edge and
|
|
74936
|
+
// leaves the others as they are.
|
|
74937
|
+
const getFindChunks = (from, to) => {
|
|
74938
|
+
if (!findText) {
|
|
74939
|
+
return null;
|
|
74940
|
+
}
|
|
74941
|
+
const chunks = [];
|
|
74942
|
+
let chunkFrom = from;
|
|
74943
|
+
while (chunkFrom < to) {
|
|
74944
|
+
const chunkIndex = Math.floor(rankOf(chunkFrom) / FIND_CHUNK_ROW_COUNT);
|
|
74945
|
+
const alignedTo = rowOf((chunkIndex + 1) * FIND_CHUNK_ROW_COUNT);
|
|
74946
|
+
const chunkTo = alignedTo < to ? alignedTo : to;
|
|
74947
|
+
const lines = [];
|
|
74948
|
+
let lineRowIndex = chunkFrom;
|
|
74949
|
+
while (lineRowIndex < chunkTo) {
|
|
74950
|
+
const item = getItemAt(lineRowIndex);
|
|
74951
|
+
// A row not held has nothing to find, but keeps its line: the rows
|
|
74952
|
+
// after it stay at their place.
|
|
74953
|
+
lines.push(item === undefined ? "" : toFindLine(findText(item, lineRowIndex)));
|
|
74954
|
+
lineRowIndex++;
|
|
74955
|
+
}
|
|
74956
|
+
chunks.push({
|
|
74957
|
+
from: chunkFrom,
|
|
74958
|
+
to: chunkTo,
|
|
74959
|
+
text: lines.join("\n")
|
|
74960
|
+
});
|
|
74961
|
+
chunkFrom = chunkTo;
|
|
74962
|
+
}
|
|
74963
|
+
return chunks;
|
|
74964
|
+
};
|
|
74852
74965
|
// The room held for this run's own rows that the window leaves out. It
|
|
74853
74966
|
// belongs to the run and not to the list: a list is not necessarily made of
|
|
74854
74967
|
// one run, and what sits before or after it (a header, rows given one by
|
|
@@ -74856,7 +74969,8 @@ const useRunRows = (store, {
|
|
|
74856
74969
|
if (windowFrom > runStart) {
|
|
74857
74970
|
rows.push(jsx(VirtualFiller, {
|
|
74858
74971
|
edge: "before",
|
|
74859
|
-
itemCount: windowFrom - runStart
|
|
74972
|
+
itemCount: windowFrom - runStart,
|
|
74973
|
+
findChunks: getFindChunks(runStart, windowFrom)
|
|
74860
74974
|
}, "navi-list-filler-before"));
|
|
74861
74975
|
}
|
|
74862
74976
|
const renderItemState = {
|
|
@@ -74958,11 +75072,20 @@ const useRunRows = (store, {
|
|
|
74958
75072
|
if (runEnd > windowTo) {
|
|
74959
75073
|
rows.push(jsx(VirtualFiller, {
|
|
74960
75074
|
edge: "after",
|
|
74961
|
-
itemCount: runEnd - windowTo
|
|
75075
|
+
itemCount: runEnd - windowTo,
|
|
75076
|
+
findChunks: getFindChunks(windowTo, runEnd)
|
|
74962
75077
|
}, "navi-list-filler-after"));
|
|
74963
75078
|
}
|
|
74964
75079
|
return rows;
|
|
74965
75080
|
};
|
|
75081
|
+
// One row, one line: a line break inside it would push every row below off
|
|
75082
|
+
// its place.
|
|
75083
|
+
const toFindLine = text => {
|
|
75084
|
+
if (text === undefined || text === null) {
|
|
75085
|
+
return "";
|
|
75086
|
+
}
|
|
75087
|
+
return String(text).replace(/[\r\n]+/g, " ");
|
|
75088
|
+
};
|
|
74966
75089
|
|
|
74967
75090
|
// A run's row that has not arrived, standing where the real one will. It never
|
|
74968
75091
|
// reaches ListItemUI (see ListItemSkeletonResolver), so it is drawn among the
|
|
@@ -83710,8 +83833,22 @@ function WheelUI(props) {
|
|
|
83710
83833
|
...style
|
|
83711
83834
|
};
|
|
83712
83835
|
|
|
83713
|
-
// Main-axis size of the viewport, from the orientation.
|
|
83714
|
-
|
|
83836
|
+
// Main-axis size of the viewport, from the orientation. Cached like the row
|
|
83837
|
+
// size: applyOffset runs right after each window commit, where reading the
|
|
83838
|
+
// box would force a layout at every row crossing. The observer below keeps it
|
|
83839
|
+
// current; 0 (no box yet) is never kept, so the next read measures again.
|
|
83840
|
+
const viewportSizeRef = useRef(0);
|
|
83841
|
+
const measureViewportMain = vp => isHorizontal ? vp.clientWidth : vp.clientHeight;
|
|
83842
|
+
const viewportMain = vp => {
|
|
83843
|
+
if (viewportSizeRef.current > 0) {
|
|
83844
|
+
return viewportSizeRef.current;
|
|
83845
|
+
}
|
|
83846
|
+
const size = measureViewportMain(vp);
|
|
83847
|
+
if (size > 0) {
|
|
83848
|
+
viewportSizeRef.current = size;
|
|
83849
|
+
}
|
|
83850
|
+
return size;
|
|
83851
|
+
};
|
|
83715
83852
|
|
|
83716
83853
|
// Row size (px). Rows are uniform, so one measurement (cached) drives all the
|
|
83717
83854
|
// geometry — positions are then computed from value indices, not read off the
|
|
@@ -83782,6 +83919,21 @@ function WheelUI(props) {
|
|
|
83782
83919
|
applyOffset(vp);
|
|
83783
83920
|
}
|
|
83784
83921
|
};
|
|
83922
|
+
// Refreshes the cached viewport size (see viewportMain). Read in the observer
|
|
83923
|
+
// callback, where the layout is already clean. A new size moves the center
|
|
83924
|
+
// window, so the offset is re-applied with it.
|
|
83925
|
+
useLayoutEffect(() => {
|
|
83926
|
+
const vp = getViewport();
|
|
83927
|
+
viewportSizeRef.current = 0;
|
|
83928
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
83929
|
+
viewportSizeRef.current = measureViewportMain(vp);
|
|
83930
|
+
applyOffset(vp);
|
|
83931
|
+
});
|
|
83932
|
+
resizeObserver.observe(vp);
|
|
83933
|
+
return () => {
|
|
83934
|
+
resizeObserver.disconnect();
|
|
83935
|
+
};
|
|
83936
|
+
}, [isHorizontal]);
|
|
83785
83937
|
|
|
83786
83938
|
// The value index at the center for the current position.
|
|
83787
83939
|
const centeredIndex = vp => {
|