@jsenv/navi 0.29.439 → 0.29.441
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 +408 -125
- package/dist/dev/jsenv_navi.js.map +8 -8
- package/dist/jsenv_navi.js +408 -125
- package/dist/jsenv_navi.js.map +8 -8
- package/docs/scroll.md +8 -7
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -13381,6 +13381,53 @@ const findControlProxy = (el) => {
|
|
|
13381
13381
|
return null;
|
|
13382
13382
|
};
|
|
13383
13383
|
|
|
13384
|
+
/**
|
|
13385
|
+
* Tells a typed field's control that the user gave up on what they typed:
|
|
13386
|
+
* `navi_cancel` with its reason — Escape, or leaving the field invalid. What
|
|
13387
|
+
* giving up means is the control's to decide (onnavi_cancel in
|
|
13388
|
+
* control_hooks.jsx: resetOnCancel, cancelOnEscape, cancelOnBlurInvalid).
|
|
13389
|
+
*
|
|
13390
|
+
* Leaving a field empty is not a cancel: the "change" of that same blur has
|
|
13391
|
+
* already sent the empty value. A field that must not be left empty says
|
|
13392
|
+
* `required`, and leaving it is then a blur_invalid.
|
|
13393
|
+
*/
|
|
13394
|
+
const addInputCancel = (input, controller) => {
|
|
13395
|
+
const dispatchCancel = (detail) => {
|
|
13396
|
+
dispatchInternalCustomEvent(input, "navi_cancel", detail);
|
|
13397
|
+
};
|
|
13398
|
+
const onkeydown = (e) => {
|
|
13399
|
+
if (e.key !== "Escape") {
|
|
13400
|
+
return;
|
|
13401
|
+
}
|
|
13402
|
+
if (controller.rules.callout.callout) {
|
|
13403
|
+
// Escape closes the open callout first (callout.js listens on its anchor).
|
|
13404
|
+
return;
|
|
13405
|
+
}
|
|
13406
|
+
dispatchCancel({ event: e, reason: "escape_key" });
|
|
13407
|
+
};
|
|
13408
|
+
const onblur = (e) => {
|
|
13409
|
+
if (input.closest("[inert]")) {
|
|
13410
|
+
// The page took the field away (an edition ending, a side swapped out)
|
|
13411
|
+
// and the focus went with it: the user did not leave it.
|
|
13412
|
+
return;
|
|
13413
|
+
}
|
|
13414
|
+
const { failedConstraintInfo } = controller.rules.validation;
|
|
13415
|
+
if (failedConstraintInfo) {
|
|
13416
|
+
dispatchCancel({
|
|
13417
|
+
event: e,
|
|
13418
|
+
reason: "blur_invalid",
|
|
13419
|
+
failedConstraintInfo,
|
|
13420
|
+
});
|
|
13421
|
+
}
|
|
13422
|
+
};
|
|
13423
|
+
input.addEventListener("keydown", onkeydown);
|
|
13424
|
+
input.addEventListener("blur", onblur);
|
|
13425
|
+
return () => {
|
|
13426
|
+
input.removeEventListener("keydown", onkeydown);
|
|
13427
|
+
input.removeEventListener("blur", onblur);
|
|
13428
|
+
};
|
|
13429
|
+
};
|
|
13430
|
+
|
|
13384
13431
|
const addInputEffect = (
|
|
13385
13432
|
input,
|
|
13386
13433
|
callback,
|
|
@@ -13569,11 +13616,15 @@ const listenInputStateChange = (
|
|
|
13569
13616
|
callback(e);
|
|
13570
13617
|
};
|
|
13571
13618
|
input.addEventListener("input", oninput);
|
|
13572
|
-
|
|
13619
|
+
// Capture: the guard is set before the control reacts to the key. Its
|
|
13620
|
+
// reaction can end what the field is in (Enter sends, Escape cancels, an
|
|
13621
|
+
// edition closes and hands the focus away), and the "change" of that blur
|
|
13622
|
+
// arrives in the microtasks between two listeners of this keydown.
|
|
13623
|
+
input.addEventListener("keydown", onkeydown, { capture: true });
|
|
13573
13624
|
input.addEventListener("change", onchange);
|
|
13574
13625
|
addTeardown(() => {
|
|
13575
13626
|
input.removeEventListener("input", oninput);
|
|
13576
|
-
input.removeEventListener("keydown", onkeydown);
|
|
13627
|
+
input.removeEventListener("keydown", onkeydown, { capture: true });
|
|
13577
13628
|
input.removeEventListener("change", onchange);
|
|
13578
13629
|
});
|
|
13579
13630
|
|
|
@@ -46704,16 +46755,19 @@ const useControlProps = (props, {
|
|
|
46704
46755
|
// ref so the effect always fires the current render's reaction.
|
|
46705
46756
|
const applyEventReactionRef = useRef();
|
|
46706
46757
|
applyEventReactionRef.current = applyEventReaction;
|
|
46758
|
+
const isTypedField = controlType === "input" && !isCheckable && props.type !== "range";
|
|
46707
46759
|
const refCallback = useCallback(field => {
|
|
46708
|
-
|
|
46709
|
-
|
|
46710
|
-
}
|
|
46711
|
-
return addInputEffect(field, e => applyEventReactionRef.current("naviChange", e), {
|
|
46760
|
+
const removeInputCancel = isTypedField ? addInputCancel(field, uiStateController) : undefined;
|
|
46761
|
+
const removeInputEffect = hasNaviChangeEventReaction && actionEvent !== "custom" ? addInputEffect(field, e => applyEventReactionRef.current("naviChange", e), {
|
|
46712
46762
|
waitForChange: actionAfterChange,
|
|
46713
46763
|
debounce: actionDebounce,
|
|
46714
46764
|
debugInteraction
|
|
46715
|
-
});
|
|
46716
|
-
|
|
46765
|
+
}) : undefined;
|
|
46766
|
+
return () => {
|
|
46767
|
+
removeInputCancel?.();
|
|
46768
|
+
removeInputEffect?.();
|
|
46769
|
+
};
|
|
46770
|
+
}, [isTypedField, actionEvent, actionAfterChange, actionDebounce, hasNaviChangeEventReaction, ref]);
|
|
46717
46771
|
const refComposed = useComposeElementRef(refCallback, ref);
|
|
46718
46772
|
const onPaste = e => {
|
|
46719
46773
|
props.onPaste?.(e);
|
|
@@ -47676,7 +47730,7 @@ const useInteractiveProps = (props, {
|
|
|
47676
47730
|
}
|
|
47677
47731
|
if (
|
|
47678
47732
|
// error prevent cancellation until the user closes it (or something closes it)
|
|
47679
|
-
e.detail.failedConstraintInfo.
|
|
47733
|
+
e.detail.failedConstraintInfo.status === "error" && e.detail.failedConstraintInfo.reportStatus !== "closed") {
|
|
47680
47734
|
return;
|
|
47681
47735
|
}
|
|
47682
47736
|
}
|
|
@@ -61287,21 +61341,51 @@ const Editable = props => {
|
|
|
61287
61341
|
editingPreviousRef.current = editing;
|
|
61288
61342
|
}
|
|
61289
61343
|
|
|
61290
|
-
//
|
|
61344
|
+
// The field is mounted the whole time, so edition starting is when it
|
|
61345
|
+
// arrives, and Editable places the focus itself: a mount-time autofocus
|
|
61346
|
+
// would only ever see the first `editing`. Starting edition is a press aimed
|
|
61347
|
+
// at typing, so the field takes the keyboard whatever asked for it — a
|
|
61348
|
+
// click, Enter, a letter key.
|
|
61349
|
+
const focusedBeforeEditionRef = useRef(null);
|
|
61291
61350
|
useLayoutEffect(() => {
|
|
61351
|
+
const field = ref.current;
|
|
61292
61352
|
if (!editing) {
|
|
61353
|
+
const focusedBeforeEdition = focusedBeforeEditionRef.current;
|
|
61354
|
+
focusedBeforeEditionRef.current = null;
|
|
61355
|
+
if (!focusedBeforeEdition || !focusedBeforeEdition.isConnected) {
|
|
61356
|
+
return;
|
|
61357
|
+
}
|
|
61358
|
+
// The field just went inert while holding the focus (Enter, Escape, the
|
|
61359
|
+
// action ending): the focus goes back to where edition was asked from.
|
|
61360
|
+
// Focus the user took away, the blur that ended edition, stays where
|
|
61361
|
+
// they put it — on the body included.
|
|
61362
|
+
if (document.activeElement !== field) {
|
|
61363
|
+
return;
|
|
61364
|
+
}
|
|
61365
|
+
moveFocusTo(focusedBeforeEdition);
|
|
61293
61366
|
return;
|
|
61294
61367
|
}
|
|
61295
|
-
|
|
61296
|
-
if (
|
|
61297
|
-
|
|
61298
|
-
|
|
61299
|
-
|
|
61300
|
-
|
|
61301
|
-
|
|
61302
|
-
|
|
61303
|
-
|
|
61304
|
-
|
|
61368
|
+
focusedBeforeEditionRef.current = document.activeElement;
|
|
61369
|
+
if (!valueSignal) {
|
|
61370
|
+
// An edition starts from the value, not from what a cancelled one left
|
|
61371
|
+
// typed in the field. Done before the focus: written into a focused
|
|
61372
|
+
// field, it would read as a change to send (see input_effect.js).
|
|
61373
|
+
dispatchRequestResetUIState(field, editing.event);
|
|
61374
|
+
}
|
|
61375
|
+
moveFocusTo(field);
|
|
61376
|
+
const initialValue = editing.event?.detail?.initialValue;
|
|
61377
|
+
if (initialValue !== undefined) {
|
|
61378
|
+
// The key that started edition is its first keystroke.
|
|
61379
|
+
field.value = initialValue;
|
|
61380
|
+
field.dispatchEvent(new CustomEvent("input", {
|
|
61381
|
+
bubbles: false
|
|
61382
|
+
}));
|
|
61383
|
+
return;
|
|
61384
|
+
}
|
|
61385
|
+
if (autoFocusSelect) {
|
|
61386
|
+
field.select();
|
|
61387
|
+
// Keep the beginning of the text visible instead of scrolling to the end
|
|
61388
|
+
field.scrollLeft = 0;
|
|
61305
61389
|
}
|
|
61306
61390
|
}, [editing]);
|
|
61307
61391
|
const controlProps = {
|
|
@@ -61310,9 +61394,6 @@ const Editable = props => {
|
|
|
61310
61394
|
name,
|
|
61311
61395
|
value,
|
|
61312
61396
|
valueSignal,
|
|
61313
|
-
autoFocus: editing,
|
|
61314
|
-
autoFocusVisible: true,
|
|
61315
|
-
autoFocusSelect,
|
|
61316
61397
|
cancelOnEscape: true,
|
|
61317
61398
|
cancelOnBlurInvalid: true,
|
|
61318
61399
|
constraints,
|
|
@@ -61332,6 +61413,11 @@ const Editable = props => {
|
|
|
61332
61413
|
});
|
|
61333
61414
|
},
|
|
61334
61415
|
onBlur: e => {
|
|
61416
|
+
if (!editing) {
|
|
61417
|
+
// The focus handed back once edition ended (see the effect above):
|
|
61418
|
+
// the edition is already over, this blur is its consequence.
|
|
61419
|
+
return;
|
|
61420
|
+
}
|
|
61335
61421
|
let inputValue;
|
|
61336
61422
|
const valueWhenEditStart = valueWhenEditStartRef.current;
|
|
61337
61423
|
let inputValueWhenEditStart;
|
|
@@ -61398,7 +61484,7 @@ const Editable = props => {
|
|
|
61398
61484
|
// - input not focusable (via keyboard or anything)
|
|
61399
61485
|
// - cannot be interacted with pointer (click, hover, etc)
|
|
61400
61486
|
// - is ignored by screen readers
|
|
61401
|
-
inert: editing
|
|
61487
|
+
inert: !editing,
|
|
61402
61488
|
"data-editing": editing ? "" : undefined,
|
|
61403
61489
|
children: control
|
|
61404
61490
|
})]
|
|
@@ -72732,9 +72818,16 @@ document.addEventListener(
|
|
|
72732
72818
|
* progress. A write is an instant scroll, and the browser abandons for it any
|
|
72733
72819
|
* scroll it is playing on its own, short of where that one was going: a key
|
|
72734
72820
|
* (Home, PageDown, Space), a smooth `scrollTo` or `scrollIntoView`, the status
|
|
72735
|
-
* bar tap on iOS
|
|
72736
|
-
*
|
|
72737
|
-
*
|
|
72821
|
+
* bar tap on iOS. A scroll following a hand is not abandoned: the wheel's next
|
|
72822
|
+
* turn carries on, and on most engines a finger keeps dragging from wherever
|
|
72823
|
+
* the write left it, and the fling it leaves behind once it lifts carries on
|
|
72824
|
+
* from there too (Chrome under DevTools' touch emulation: flings written to at
|
|
72825
|
+
* every move of a list's window went as far as flings never written to).
|
|
72826
|
+
*
|
|
72827
|
+
* A fling must not be taken for a scroll the browser plays on its own. A list
|
|
72828
|
+
* then leaves uncorrected, for the whole fling, the room its fillers lose above
|
|
72829
|
+
* the screen as its window slides: in wematch, cards jumping half a screen
|
|
72830
|
+
* several times per fling.
|
|
72738
72831
|
*
|
|
72739
72832
|
* Not on iOS. Its finger places the content from where the drag began, so a
|
|
72740
72833
|
* write under it is undone at the finger's next move — or, when the finger
|
|
@@ -72742,11 +72835,15 @@ document.addEventListener(
|
|
|
72742
72835
|
* stops on any write during its momentum, as the status bar's scroll to the
|
|
72743
72836
|
* top does (Safari 26.5 on the simulator: flings of 1700px stopped 130 to 340px
|
|
72744
72837
|
* after the lift, 3 in 3; a write 50ms before the lift stopped 2 flings in 3).
|
|
72745
|
-
* No finger there is a hand a write could hold to
|
|
72746
|
-
*
|
|
72747
|
-
*
|
|
72748
|
-
*
|
|
72749
|
-
*
|
|
72838
|
+
* No finger there is a hand a write could hold to, nor the fling it leaves.
|
|
72839
|
+
*
|
|
72840
|
+
* A scroll is followed from its first "scroll" event to its "scrollend". It is
|
|
72841
|
+
* a finger's while a finger is down, and still once the finger lifts if a
|
|
72842
|
+
* finger moved it around the time it started or since: the fling goes on in
|
|
72843
|
+
* the drag's own scroll, or in a scroll of its own that Chrome starts right
|
|
72844
|
+
* after the drag's "scrollend". What counts is a scroll the finger moved, not
|
|
72845
|
+
* a lift: a tap on a button calling a smooth `scrollTo` lifts a finger that
|
|
72846
|
+
* moved nothing. A scroll is a wheel's when a wheel turned around the time it
|
|
72750
72847
|
* started or since. A finger is down from its touchstart to its touchend,
|
|
72751
72848
|
* heard on what it touched: a list removes the items scrolled out of its window
|
|
72752
72849
|
* while the finger is still on one of them, and the touchend of a removed
|
|
@@ -72760,6 +72857,10 @@ document.addEventListener(
|
|
|
72760
72857
|
// next one. A turn further from it moved nothing (a wheel at an edge) and is no
|
|
72761
72858
|
// hand on a scroll that came from elsewhere.
|
|
72762
72859
|
const WHEEL_REACH = 100;
|
|
72860
|
+
// The fling Chrome plays as a scroll of its own starts a frame or two after the
|
|
72861
|
+
// drag's last move (DevTools' touch emulation, 4× CPU: 12ms after the drag's
|
|
72862
|
+
// "scrollend").
|
|
72863
|
+
const FLING_REACH = 150;
|
|
72763
72864
|
// iOS's touch handling, the one engine whose finger drops writes (see above).
|
|
72764
72865
|
const FINGER_KEEPS_WRITES = !window.CSS.supports(
|
|
72765
72866
|
"-webkit-touch-callout",
|
|
@@ -72769,6 +72870,7 @@ const FINGER_KEEPS_WRITES = !window.CSS.supports(
|
|
|
72769
72870
|
const flightMap = new WeakMap();
|
|
72770
72871
|
let fingersDown = 0;
|
|
72771
72872
|
let wheelTimeStamp = -Infinity;
|
|
72873
|
+
let fingerScrollTimeStamp = -Infinity;
|
|
72772
72874
|
|
|
72773
72875
|
/**
|
|
72774
72876
|
* @param {Element} scrollerEl
|
|
@@ -72779,7 +72881,10 @@ const wouldInterruptScroll = (scrollerEl) => {
|
|
|
72779
72881
|
if (!flight) {
|
|
72780
72882
|
return false;
|
|
72781
72883
|
}
|
|
72782
|
-
if (
|
|
72884
|
+
if (
|
|
72885
|
+
fingersDown > 0 ||
|
|
72886
|
+
fingerScrollTimeStamp > flight.timeStamp - FLING_REACH
|
|
72887
|
+
) {
|
|
72783
72888
|
return !FINGER_KEEPS_WRITES;
|
|
72784
72889
|
}
|
|
72785
72890
|
if (wheelTimeStamp > flight.timeStamp - WHEEL_REACH) {
|
|
@@ -72806,6 +72911,9 @@ if ("onscrollend" in window) {
|
|
|
72806
72911
|
document.addEventListener(
|
|
72807
72912
|
"scroll",
|
|
72808
72913
|
(e) => {
|
|
72914
|
+
if (fingersDown > 0) {
|
|
72915
|
+
fingerScrollTimeStamp = e.timeStamp;
|
|
72916
|
+
}
|
|
72809
72917
|
const scroller = asScroller(e.target);
|
|
72810
72918
|
if (flightMap.has(scroller)) {
|
|
72811
72919
|
return;
|
|
@@ -73136,7 +73244,13 @@ const createListItems = () => {
|
|
|
73136
73244
|
// One, as a rule; a child that renders several items keeps them in the order
|
|
73137
73245
|
// they first rendered, which is all it can be told.
|
|
73138
73246
|
const ownerIdsBySlot = new Map();
|
|
73247
|
+
// What each run answers about the items it holds, drawn or not: where the
|
|
73248
|
+
// one of an id sits, which one sits at an index.
|
|
73139
73249
|
const locatorByOwner = new Map();
|
|
73250
|
+
// The room each item took along the scroll axis the last time it was drawn,
|
|
73251
|
+
// by id: what a filler holds for it once it is not (see rememberItemRooms in
|
|
73252
|
+
// list.jsx). A number per item drawn while the list is mounted.
|
|
73253
|
+
const roomById = new Map();
|
|
73140
73254
|
// The slots as the tree reads, first to last, and where each stands in it.
|
|
73141
73255
|
// Rebuilt once a walk has changed the tree, read to place the owners.
|
|
73142
73256
|
const slotWalk = [];
|
|
@@ -73485,6 +73599,7 @@ const createListItems = () => {
|
|
|
73485
73599
|
holdViewStill: () => {},
|
|
73486
73600
|
horizontal: false,
|
|
73487
73601
|
virtualItemSizeSignal: null,
|
|
73602
|
+
roomById,
|
|
73488
73603
|
renderSkeleton: undefined,
|
|
73489
73604
|
// The children a walk stands over, in order — said in one call, before any
|
|
73490
73605
|
// of them renders, so that what a child asks next is answered against the
|
|
@@ -73516,22 +73631,33 @@ const createListItems = () => {
|
|
|
73516
73631
|
// Whether any run of items lives in this list: what makes a render window
|
|
73517
73632
|
// mean anything (see List's renderBudget).
|
|
73518
73633
|
hasRuns: () => locatorByOwner.size > 0,
|
|
73519
|
-
setItemLocator: (ownerId,
|
|
73520
|
-
locatorByOwner.set(ownerId,
|
|
73634
|
+
setItemLocator: (ownerId, locator) => {
|
|
73635
|
+
locatorByOwner.set(ownerId, locator);
|
|
73521
73636
|
},
|
|
73522
73637
|
dropItemLocator: (ownerId) => {
|
|
73523
73638
|
locatorByOwner.delete(ownerId);
|
|
73524
73639
|
},
|
|
73525
73640
|
// Where the item named by that id sits, asked of whoever holds it.
|
|
73526
73641
|
locateItem: (id) => {
|
|
73527
|
-
for (const
|
|
73528
|
-
const index =
|
|
73642
|
+
for (const locator of locatorByOwner.values()) {
|
|
73643
|
+
const index = locator.indexOf(id);
|
|
73529
73644
|
if (index !== null) {
|
|
73530
73645
|
return index;
|
|
73531
73646
|
}
|
|
73532
73647
|
}
|
|
73533
73648
|
return null;
|
|
73534
73649
|
},
|
|
73650
|
+
// The room the item at that index took when it was last drawn, or
|
|
73651
|
+
// undefined: never drawn, not held, or not an item of a run.
|
|
73652
|
+
roomAt: (index) => {
|
|
73653
|
+
for (const locator of locatorByOwner.values()) {
|
|
73654
|
+
const id = locator.idAt(index);
|
|
73655
|
+
if (id !== undefined) {
|
|
73656
|
+
return roomById.get(id);
|
|
73657
|
+
}
|
|
73658
|
+
}
|
|
73659
|
+
return undefined;
|
|
73660
|
+
},
|
|
73535
73661
|
// The place the owner's items start at — read from a signal, so that the
|
|
73536
73662
|
// owner is rendered again when it moves (see the top of this file). Asked
|
|
73537
73663
|
// on every render, and answered without a second look for as long as the
|
|
@@ -75648,6 +75774,8 @@ const useListScrollSync = ({
|
|
|
75648
75774
|
// state: the render moves it where the list is held (holdWindow) without a
|
|
75649
75775
|
// commit of its own.
|
|
75650
75776
|
const renderWindowRef = useRef(null);
|
|
75777
|
+
// A window was asked for that no render has drawn yet (see settleWindow).
|
|
75778
|
+
const windowAskedRef = useRef(false);
|
|
75651
75779
|
const windowLeavesItemsOut = () => {
|
|
75652
75780
|
const renderWindow = renderWindowRef.current;
|
|
75653
75781
|
if (!renderWindow) {
|
|
@@ -75742,6 +75870,8 @@ const useListScrollSync = ({
|
|
|
75742
75870
|
return;
|
|
75743
75871
|
}
|
|
75744
75872
|
captureAnchor();
|
|
75873
|
+
// What was measured is the room of an item alone on its line.
|
|
75874
|
+
listItems.roomById.clear();
|
|
75745
75875
|
itemsPerLineRef.current = itemsPerLineNow;
|
|
75746
75876
|
setItemsPerLine(itemsPerLineNow);
|
|
75747
75877
|
};
|
|
@@ -75823,6 +75953,25 @@ const useListScrollSync = ({
|
|
|
75823
75953
|
}
|
|
75824
75954
|
}
|
|
75825
75955
|
}
|
|
75956
|
+
// The items about to leave the window are still drawn: the room each takes
|
|
75957
|
+
// is kept, and the filler holding its place from then on holds exactly that
|
|
75958
|
+
// (see VirtualFiller). Nothing above the screen then changes as the window
|
|
75959
|
+
// slides, which matters most where the anchoring cannot write: a scroll in
|
|
75960
|
+
// flight (see holdAnchorStill). Every item drawn is read, since any may be
|
|
75961
|
+
// the next to leave. A room kept is the room of the last drawing: an item
|
|
75962
|
+
// that changed since is measured again when drawn again, and the anchoring
|
|
75963
|
+
// holds the view across the difference. Items laid side by side in a grid
|
|
75964
|
+
// share a line, which the fillers hold at the one size.
|
|
75965
|
+
const rememberItemRooms = () => {
|
|
75966
|
+
if (!ref.current || itemsPerLineRef.current !== 1) {
|
|
75967
|
+
return;
|
|
75968
|
+
}
|
|
75969
|
+
const listEl = getListEl();
|
|
75970
|
+
if (!listEl) {
|
|
75971
|
+
return;
|
|
75972
|
+
}
|
|
75973
|
+
readItemRooms(listEl, horizontal, listItems.roomById);
|
|
75974
|
+
};
|
|
75826
75975
|
const updateRenderWindow = (newStart, newEnd, reason) => {
|
|
75827
75976
|
const {
|
|
75828
75977
|
start,
|
|
@@ -75831,6 +75980,7 @@ const useListScrollSync = ({
|
|
|
75831
75980
|
if (newStart === start && newEnd === end) {
|
|
75832
75981
|
return null;
|
|
75833
75982
|
}
|
|
75983
|
+
rememberItemRooms();
|
|
75834
75984
|
captureAnchor();
|
|
75835
75985
|
debugScroll(`updateRenderWindow(${newStart}, ${newEnd}, "${reason}")`);
|
|
75836
75986
|
const renderWindow = {
|
|
@@ -75838,6 +75988,7 @@ const useListScrollSync = ({
|
|
|
75838
75988
|
end: newEnd
|
|
75839
75989
|
};
|
|
75840
75990
|
renderWindowRef.current = renderWindow;
|
|
75991
|
+
windowAskedRef.current = true;
|
|
75841
75992
|
setRenderWindow(renderWindow);
|
|
75842
75993
|
return renderWindow;
|
|
75843
75994
|
};
|
|
@@ -76249,6 +76400,20 @@ const useListScrollSync = ({
|
|
|
76249
76400
|
}
|
|
76250
76401
|
const scrollerEl = getScroller();
|
|
76251
76402
|
if (openAt === "end") {
|
|
76403
|
+
const total = listItems.totalSignal.peek();
|
|
76404
|
+
const {
|
|
76405
|
+
start,
|
|
76406
|
+
end
|
|
76407
|
+
} = renderWindowRef.current;
|
|
76408
|
+
if (end < total) {
|
|
76409
|
+
// The window was framed before the runs said how many items there are
|
|
76410
|
+
// (see holdWindow): the end of the document is the end of a filler.
|
|
76411
|
+
// Aimed at the last items first, like a named item.
|
|
76412
|
+
const windowSize = end - start;
|
|
76413
|
+
const wantedStart = total - windowSize < 0 ? 0 : total - windowSize;
|
|
76414
|
+
updateRenderWindow(wantedStart, total, "opening at the end");
|
|
76415
|
+
return;
|
|
76416
|
+
}
|
|
76252
76417
|
sendHeldScroll(horizontal ? scrollerEl.scrollWidth : scrollerEl.scrollHeight);
|
|
76253
76418
|
return;
|
|
76254
76419
|
}
|
|
@@ -76326,6 +76491,13 @@ const useListScrollSync = ({
|
|
|
76326
76491
|
if (stage === "steady") {
|
|
76327
76492
|
return;
|
|
76328
76493
|
}
|
|
76494
|
+
if (windowAskedRef.current) {
|
|
76495
|
+
// The hold has just aimed the window where the list opens — its end, an
|
|
76496
|
+
// item it found — and the next commit draws it. Sized now, the window
|
|
76497
|
+
// would weigh the items there by the ones drawn here, nowhere near them:
|
|
76498
|
+
// the one-line items at the start of a thread opening on its cards.
|
|
76499
|
+
return;
|
|
76500
|
+
}
|
|
76329
76501
|
const initialBudget = initialBudgetRef.current;
|
|
76330
76502
|
if (initialBudget && !paintedRef.current) {
|
|
76331
76503
|
if (stage === "picture") {
|
|
@@ -76667,9 +76839,9 @@ const useListScrollSync = ({
|
|
|
76667
76839
|
if (wouldInterruptScroll(scrollerEl)) {
|
|
76668
76840
|
// Nobody is looking at items standing still: they are passing by, on
|
|
76669
76841
|
// their way somewhere the browser takes them (a key, a smooth scrollTo,
|
|
76670
|
-
// the status bar, a fling). The write would stop that scroll short
|
|
76671
|
-
// where it was going. Left alone, the items drawn above push the rest
|
|
76672
|
-
// `drift` while all of it moves, and the scroll lands.
|
|
76842
|
+
// the status bar, a fling on iOS). The write would stop that scroll short
|
|
76843
|
+
// of where it was going. Left alone, the items drawn above push the rest
|
|
76844
|
+
// by `drift` while all of it moves, and the scroll lands.
|
|
76673
76845
|
return;
|
|
76674
76846
|
}
|
|
76675
76847
|
scrolledByListRef.current = true;
|
|
@@ -76798,8 +76970,14 @@ const useListScrollSync = ({
|
|
|
76798
76970
|
bandEnd = walkAfter(hold.index, screenSize - hold.above, pixelsOf).index;
|
|
76799
76971
|
forward = true;
|
|
76800
76972
|
} else {
|
|
76801
|
-
|
|
76802
|
-
|
|
76973
|
+
// In the room a filler holds, an item is counted at what the filler
|
|
76974
|
+
// gives it.
|
|
76975
|
+
const fillerSizeOf = itemSize > 0 ? index => {
|
|
76976
|
+
const room = listItems.roomAt(index);
|
|
76977
|
+
return room === undefined ? itemSize : room;
|
|
76978
|
+
} : null;
|
|
76979
|
+
bandStart = findBandStart(items, bandFrom, fillerSizeOf, total);
|
|
76980
|
+
bandEnd = bandTo > bandFrom ? findBandEnd(items, bandTo, fillerSizeOf, total) : bandStart;
|
|
76803
76981
|
forward = scrollDirectionRef.current > 0;
|
|
76804
76982
|
}
|
|
76805
76983
|
if (bandStart > total) {
|
|
@@ -77057,6 +77235,8 @@ const useListScrollSync = ({
|
|
|
77057
77235
|
};
|
|
77058
77236
|
}, [scrollerElResolved]);
|
|
77059
77237
|
holdWindow();
|
|
77238
|
+
// Whatever was asked for until now is what this render draws.
|
|
77239
|
+
windowAskedRef.current = false;
|
|
77060
77240
|
const {
|
|
77061
77241
|
start: windowStart,
|
|
77062
77242
|
end: windowEnd
|
|
@@ -77802,12 +77982,49 @@ const readWindowGeometry = (scrollerEl, listEl, horizontal) => {
|
|
|
77802
77982
|
items
|
|
77803
77983
|
};
|
|
77804
77984
|
};
|
|
77985
|
+
// The room each item drawn takes along the scroll axis (see
|
|
77986
|
+
// rememberItemRooms): up to the next item when that one is drawn too, so that
|
|
77987
|
+
// what stands between them — a separator, the label of a group the next one
|
|
77988
|
+
// opens — is counted; its own box otherwise. A stand-in is not measured: it
|
|
77989
|
+
// takes the room it is given, not the room of its item.
|
|
77990
|
+
const readItemRooms = (listEl, horizontal, roomById) => {
|
|
77991
|
+
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
|
+
for (const itemEl of listEl.querySelectorAll(`[${LIST_ITEM_INDEX_ATTRIBUTE}]`)) {
|
|
77999
|
+
if (!isOwnItem(itemEl, listEl)) {
|
|
78000
|
+
continue;
|
|
78001
|
+
}
|
|
78002
|
+
const rect = itemEl.getBoundingClientRect();
|
|
78003
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
78004
|
+
continue;
|
|
78005
|
+
}
|
|
78006
|
+
const item = {
|
|
78007
|
+
id: getItemName(itemEl),
|
|
78008
|
+
index: Number(itemEl.getAttribute(LIST_ITEM_INDEX_ATTRIBUTE)),
|
|
78009
|
+
from: horizontal ? rect.left : rect.top,
|
|
78010
|
+
to: horizontal ? rect.right : rect.bottom
|
|
78011
|
+
};
|
|
78012
|
+
if (previous) {
|
|
78013
|
+
keepRoom(previous, item);
|
|
78014
|
+
}
|
|
78015
|
+
previous = item;
|
|
78016
|
+
}
|
|
78017
|
+
if (previous) {
|
|
78018
|
+
keepRoom(previous, null);
|
|
78019
|
+
}
|
|
78020
|
+
};
|
|
77805
78021
|
// The first item the screen shows from `position` on, and the item after the
|
|
77806
78022
|
// last one it shows up to `position` (exclusive, like the window's end). An
|
|
77807
78023
|
// item drawn is read off its box; in the room a filler holds for items not
|
|
77808
|
-
// drawn,
|
|
77809
|
-
// only way a scroll position there says which item it is.
|
|
77810
|
-
|
|
78024
|
+
// drawn, each item is counted at the size the filler gives it (`sizeOf`) —
|
|
78025
|
+
// the only way a scroll position there says which item it is. Without a size
|
|
78026
|
+
// (`null`), the room a filler holds says nothing.
|
|
78027
|
+
const findBandStart = (items, position, sizeOf, total) => {
|
|
77811
78028
|
let low = 0;
|
|
77812
78029
|
let high = items.length;
|
|
77813
78030
|
while (low < high) {
|
|
@@ -77821,29 +78038,28 @@ const findBandStart = (items, position, itemSize) => {
|
|
|
77821
78038
|
const next = items[low];
|
|
77822
78039
|
const previous = items[low - 1];
|
|
77823
78040
|
if (!next) {
|
|
77824
|
-
if (!
|
|
78041
|
+
if (!sizeOf) {
|
|
77825
78042
|
return previous.index + 1;
|
|
77826
78043
|
}
|
|
77827
|
-
return previous.index + 1
|
|
78044
|
+
return findIndexAfter(previous.index + 1, previous.to, position, sizeOf, total);
|
|
77828
78045
|
}
|
|
77829
78046
|
if (next.from <= position) {
|
|
77830
78047
|
return next.index;
|
|
77831
78048
|
}
|
|
77832
78049
|
if (!previous) {
|
|
77833
|
-
if (!
|
|
78050
|
+
if (!sizeOf) {
|
|
77834
78051
|
return next.index;
|
|
77835
78052
|
}
|
|
77836
|
-
const index = next.index
|
|
78053
|
+
const index = findIndexBefore(next.index, next.from, position, sizeOf);
|
|
77837
78054
|
return index < 0 ? 0 : index;
|
|
77838
78055
|
}
|
|
77839
|
-
if (!
|
|
78056
|
+
if (!sizeOf || next.index === previous.index + 1) {
|
|
77840
78057
|
// Between two items that follow each other: a separator, a group label.
|
|
77841
78058
|
return next.index;
|
|
77842
78059
|
}
|
|
77843
|
-
|
|
77844
|
-
return index < next.index ? index : next.index;
|
|
78060
|
+
return findIndexAfter(previous.index + 1, previous.to, position, sizeOf, next.index);
|
|
77845
78061
|
};
|
|
77846
|
-
const findBandEnd = (items, position,
|
|
78062
|
+
const findBandEnd = (items, position, sizeOf, total) => {
|
|
77847
78063
|
let low = 0;
|
|
77848
78064
|
let high = items.length;
|
|
77849
78065
|
while (low < high) {
|
|
@@ -77857,24 +78073,53 @@ const findBandEnd = (items, position, itemSize) => {
|
|
|
77857
78073
|
const previous = items[low - 1];
|
|
77858
78074
|
const next = items[low];
|
|
77859
78075
|
if (!previous) {
|
|
77860
|
-
if (!
|
|
78076
|
+
if (!sizeOf) {
|
|
77861
78077
|
return next.index;
|
|
77862
78078
|
}
|
|
77863
|
-
|
|
77864
|
-
return index < 0 ? 0 : index;
|
|
78079
|
+
return findIndexBefore(next.index, next.from, position, sizeOf) + 1;
|
|
77865
78080
|
}
|
|
77866
78081
|
if (previous.to >= position) {
|
|
77867
78082
|
return previous.index + 1;
|
|
77868
78083
|
}
|
|
77869
|
-
if (!
|
|
78084
|
+
if (!sizeOf || next && next.index === previous.index + 1) {
|
|
77870
78085
|
return previous.index + 1;
|
|
77871
78086
|
}
|
|
77872
|
-
const index = previous.index +
|
|
78087
|
+
const index = findIndexAfter(previous.index + 1, previous.to, position, sizeOf, next ? next.index : total) + 1;
|
|
77873
78088
|
if (next && index > next.index) {
|
|
77874
78089
|
return next.index;
|
|
77875
78090
|
}
|
|
77876
78091
|
return index;
|
|
77877
78092
|
};
|
|
78093
|
+
// The item whose room holds `position`, the rooms laid one after the other
|
|
78094
|
+
// from `from` on, starting with the item at `index` — `limit` when `position`
|
|
78095
|
+
// is past them all.
|
|
78096
|
+
const findIndexAfter = (index, from, position, sizeOf, limit) => {
|
|
78097
|
+
let at = from;
|
|
78098
|
+
let itemIndex = index;
|
|
78099
|
+
while (itemIndex < limit) {
|
|
78100
|
+
const size = sizeOf(itemIndex);
|
|
78101
|
+
if (at + size > position) {
|
|
78102
|
+
return itemIndex;
|
|
78103
|
+
}
|
|
78104
|
+
at += size;
|
|
78105
|
+
itemIndex++;
|
|
78106
|
+
}
|
|
78107
|
+
return limit;
|
|
78108
|
+
};
|
|
78109
|
+
// The same, the rooms laid back from `to`, ending with the item before the
|
|
78110
|
+
// one at `index` — -1 when `position` is before them all.
|
|
78111
|
+
const findIndexBefore = (index, to, position, sizeOf) => {
|
|
78112
|
+
let at = to;
|
|
78113
|
+
let itemIndex = index;
|
|
78114
|
+
while (at > position) {
|
|
78115
|
+
if (itemIndex === 0) {
|
|
78116
|
+
return -1;
|
|
78117
|
+
}
|
|
78118
|
+
itemIndex--;
|
|
78119
|
+
at -= sizeOf(itemIndex);
|
|
78120
|
+
}
|
|
78121
|
+
return itemIndex;
|
|
78122
|
+
};
|
|
77878
78123
|
// How much the window holds on each side of the screen, in the budget's unit:
|
|
77879
78124
|
// read off the items' boxes where they stand, or — while the list holds itself
|
|
77880
78125
|
// on an item, and the screen is where that item is going to stand (see
|
|
@@ -78390,16 +78635,19 @@ const Fallback = ({
|
|
|
78390
78635
|
// every item it draws must not be redrawn — every item of it — because the size
|
|
78391
78636
|
// settled after the first commit.
|
|
78392
78637
|
// It holds lines: the items side by side on one take its room together, and a
|
|
78393
|
-
// last line left short takes it all the same.
|
|
78638
|
+
// last line left short takes it all the same. An item the list drew before
|
|
78639
|
+
// takes the room it took then instead (see rememberItemRooms).
|
|
78394
78640
|
const VirtualFiller = ({
|
|
78395
78641
|
edge,
|
|
78396
78642
|
itemCount,
|
|
78397
78643
|
itemsPerLine,
|
|
78644
|
+
itemsRemembered,
|
|
78645
|
+
roomRemembered,
|
|
78398
78646
|
findChunks
|
|
78399
78647
|
}) => {
|
|
78400
78648
|
const listItems = useContext(ListItemsContext);
|
|
78401
78649
|
const lineSize = listItems.virtualItemSizeSignal.value;
|
|
78402
|
-
const sizeToFill = Math.ceil(itemCount / itemsPerLine) * lineSize;
|
|
78650
|
+
const sizeToFill = Math.ceil(itemCount / itemsPerLine) * lineSize - itemsRemembered * lineSize / itemsPerLine + roomRemembered;
|
|
78403
78651
|
// A filler resizing moves what stands below it — the items on screen, when it
|
|
78404
78652
|
// holds the room of items above them — and it resizes in a commit of its own
|
|
78405
78653
|
// when the item size settles after the list has rendered: the list puts its
|
|
@@ -78417,14 +78665,14 @@ const VirtualFiller = ({
|
|
|
78417
78665
|
"navi-virtual-filler": edge,
|
|
78418
78666
|
"aria-hidden": true,
|
|
78419
78667
|
style: {
|
|
78420
|
-
"--size-to-fill": `${sizeToFill}px
|
|
78421
|
-
"--x-find-line-size": findChunks ? `${lineSize / itemsPerLine}px` : undefined
|
|
78668
|
+
"--size-to-fill": `${sizeToFill}px`
|
|
78422
78669
|
},
|
|
78423
78670
|
children: findChunks && findChunks.map(chunk => jsx("div", {
|
|
78424
78671
|
className: "navi_list_find_stand_in",
|
|
78425
78672
|
hidden: "until-found",
|
|
78426
78673
|
style: {
|
|
78427
|
-
"--x-find-line-count": chunk.to - chunk.from
|
|
78674
|
+
"--x-find-line-count": chunk.to - chunk.from,
|
|
78675
|
+
"--x-find-line-size": `${chunk.room === undefined ? lineSize / itemsPerLine : chunk.room}px`
|
|
78428
78676
|
},
|
|
78429
78677
|
children: chunk.text
|
|
78430
78678
|
}, `${chunk.from}_${chunk.to}`))
|
|
@@ -79317,16 +79565,51 @@ const useRunItems = (store, {
|
|
|
79317
79565
|
// Where an item named from outside actually sits. Only the run can answer:
|
|
79318
79566
|
// items it holds but does not draw are nowhere else — a list only knows the
|
|
79319
79567
|
// items it has drawn (they register themselves, see ListItemUI).
|
|
79320
|
-
|
|
79321
|
-
|
|
79322
|
-
|
|
79323
|
-
|
|
79324
|
-
|
|
79325
|
-
|
|
79568
|
+
const idAt = itemIndex => {
|
|
79569
|
+
if (itemIndex < runStart || itemIndex >= runEnd) {
|
|
79570
|
+
return undefined;
|
|
79571
|
+
}
|
|
79572
|
+
const item = getItemAt(itemIndex);
|
|
79573
|
+
return item === undefined ? undefined : idOf(item, itemIndex);
|
|
79574
|
+
};
|
|
79575
|
+
listItems.setItemLocator(ownerId, {
|
|
79576
|
+
indexOf: id => {
|
|
79577
|
+
let found = null;
|
|
79578
|
+
store.eachHeld((item, rank) => {
|
|
79579
|
+
const itemIndex = indexOfRank(rank);
|
|
79580
|
+
if (found === null && idOf(item, itemIndex) === id) {
|
|
79581
|
+
found = itemIndex;
|
|
79582
|
+
}
|
|
79583
|
+
});
|
|
79584
|
+
return found;
|
|
79585
|
+
},
|
|
79586
|
+
idAt
|
|
79587
|
+
});
|
|
79588
|
+
// The room an item took when the list last drew it (see rememberItemRooms),
|
|
79589
|
+
// or undefined: a filler holds an item it never drew at the item size.
|
|
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 = {
|
|
79596
|
+
room: 0,
|
|
79597
|
+
count: 0
|
|
79598
|
+
};
|
|
79599
|
+
if (listItems.roomById.size === 0) {
|
|
79600
|
+
return remembered;
|
|
79601
|
+
}
|
|
79602
|
+
let itemIndex = from;
|
|
79603
|
+
while (itemIndex < to) {
|
|
79604
|
+
const room = roomOf(itemIndex);
|
|
79605
|
+
if (room !== undefined) {
|
|
79606
|
+
remembered.room += room;
|
|
79607
|
+
remembered.count++;
|
|
79326
79608
|
}
|
|
79327
|
-
|
|
79328
|
-
|
|
79329
|
-
|
|
79609
|
+
itemIndex++;
|
|
79610
|
+
}
|
|
79611
|
+
return remembered;
|
|
79612
|
+
};
|
|
79330
79613
|
useLayoutEffect(() => {
|
|
79331
79614
|
return () => {
|
|
79332
79615
|
listItems.dropItemLocator(ownerId);
|
|
@@ -79502,7 +79785,9 @@ const useRunItems = (store, {
|
|
|
79502
79785
|
};
|
|
79503
79786
|
// The text of the items a filler stands for, cut in chunks aligned on the
|
|
79504
79787
|
// run's own ranks: the window sliding changes the chunk at its edge and
|
|
79505
|
-
// leaves the others as they are.
|
|
79788
|
+
// leaves the others as they are. A chunk's lines are all as tall as the
|
|
79789
|
+
// room its items take in the filler, so it also ends where that room
|
|
79790
|
+
// changes: a match is then found where its item will be drawn.
|
|
79506
79791
|
const getFindChunks = (from, to) => {
|
|
79507
79792
|
if (!findText) {
|
|
79508
79793
|
return null;
|
|
@@ -79512,10 +79797,11 @@ const useRunItems = (store, {
|
|
|
79512
79797
|
while (chunkFrom < to) {
|
|
79513
79798
|
const chunkIndex = Math.floor(rankOf(chunkFrom) / FIND_CHUNK_ITEM_COUNT);
|
|
79514
79799
|
const alignedTo = indexOfRank((chunkIndex + 1) * FIND_CHUNK_ITEM_COUNT);
|
|
79515
|
-
const
|
|
79800
|
+
const chunkEnd = alignedTo < to ? alignedTo : to;
|
|
79801
|
+
const room = roomOf(chunkFrom);
|
|
79516
79802
|
const lines = [];
|
|
79517
79803
|
let lineIndex = chunkFrom;
|
|
79518
|
-
while (lineIndex <
|
|
79804
|
+
while (lineIndex < chunkEnd && roomOf(lineIndex) === room) {
|
|
79519
79805
|
const item = getItemAt(lineIndex);
|
|
79520
79806
|
// An item not held has nothing to find, but keeps its line: the items
|
|
79521
79807
|
// after it stay at their place.
|
|
@@ -79524,10 +79810,11 @@ const useRunItems = (store, {
|
|
|
79524
79810
|
}
|
|
79525
79811
|
chunks.push({
|
|
79526
79812
|
from: chunkFrom,
|
|
79527
|
-
to:
|
|
79813
|
+
to: lineIndex,
|
|
79814
|
+
room,
|
|
79528
79815
|
text: lines.join("\n")
|
|
79529
79816
|
});
|
|
79530
|
-
chunkFrom =
|
|
79817
|
+
chunkFrom = lineIndex;
|
|
79531
79818
|
}
|
|
79532
79819
|
return chunks;
|
|
79533
79820
|
};
|
|
@@ -79536,10 +79823,13 @@ const useRunItems = (store, {
|
|
|
79536
79823
|
// one run, and what sits before or after it (a header, items given one by
|
|
79537
79824
|
// one) is not virtualized at all.
|
|
79538
79825
|
if (windowFrom > runStart) {
|
|
79826
|
+
const remembered = readRemembered(runStart, windowFrom);
|
|
79539
79827
|
nodes.push(jsx(VirtualFiller, {
|
|
79540
79828
|
edge: "before",
|
|
79541
79829
|
itemCount: windowFrom - runStart,
|
|
79542
79830
|
itemsPerLine: itemsPerLine,
|
|
79831
|
+
itemsRemembered: remembered.count,
|
|
79832
|
+
roomRemembered: remembered.room,
|
|
79543
79833
|
findChunks: getFindChunks(runStart, windowFrom)
|
|
79544
79834
|
}, "navi-list-filler-before"));
|
|
79545
79835
|
}
|
|
@@ -79641,10 +79931,13 @@ const useRunItems = (store, {
|
|
|
79641
79931
|
}
|
|
79642
79932
|
closeGroup();
|
|
79643
79933
|
if (runEnd > windowTo) {
|
|
79934
|
+
const remembered = readRemembered(windowTo, runEnd);
|
|
79644
79935
|
nodes.push(jsx(VirtualFiller, {
|
|
79645
79936
|
edge: "after",
|
|
79646
79937
|
itemCount: runEnd - windowTo,
|
|
79647
79938
|
itemsPerLine: itemsPerLine,
|
|
79939
|
+
itemsRemembered: remembered.count,
|
|
79940
|
+
roomRemembered: remembered.room,
|
|
79648
79941
|
findChunks: getFindChunks(windowTo, runEnd)
|
|
79649
79942
|
}, "navi-list-filler-after"));
|
|
79650
79943
|
}
|
|
@@ -80588,19 +80881,20 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
80588
80881
|
* source for is a page (see `<List.Items pageSize>`), whatever either says,
|
|
80589
80882
|
* so the first picture costs no second request.
|
|
80590
80883
|
* @param {number} [props.virtualItemSize]
|
|
80591
|
-
* The room an item
|
|
80592
|
-
* the fillers hold for
|
|
80593
|
-
*
|
|
80594
|
-
*
|
|
80595
|
-
*
|
|
80596
|
-
*
|
|
80597
|
-
*
|
|
80598
|
-
*
|
|
80599
|
-
*
|
|
80600
|
-
* guessed too
|
|
80601
|
-
* and what it guesses"). The items
|
|
80602
|
-
* window is sized on them. In a
|
|
80603
|
-
* card's, which the items side by
|
|
80884
|
+
* The room an item never drawn is held at, in px along the scroll axis: what
|
|
80885
|
+
* the fillers hold for it outside the render window, what a scroll position
|
|
80886
|
+
* inside them is read with, and the least an item on its way takes (see
|
|
80887
|
+
* `renderSkeleton`). An item the list has drawn is held at the room it took
|
|
80888
|
+
* then. Left out, it is the average of the items measured so far — once when
|
|
80889
|
+
* the list mounts, again when a popup around it opens, and after each commit
|
|
80890
|
+
* while items are held off screen. Given, it is the worst case: the size
|
|
80891
|
+
* every item has, or, in a list whose items differ, the smallest an item can
|
|
80892
|
+
* be (a thread of one-line items and cards: the one-line size) — an item
|
|
80893
|
+
* guessed too small builds one more, an item guessed too big leaves a blank
|
|
80894
|
+
* (see docs/scroll.md, "What the list knows, and what it guesses"). The items
|
|
80895
|
+
* drawn are measured either way: the render window is sized on them. In a
|
|
80896
|
+
* grid (`columns`), the size is a line's: a card's, which the items side by
|
|
80897
|
+
* side on it share, and the lines drawn are held at it too.
|
|
80604
80898
|
* @param {"self"|"parent"|"document"|Element|{current: Element}} [props.scroller="self"]
|
|
80605
80899
|
* Which box scrolls — and with it, which box the render window follows and
|
|
80606
80900
|
* which box a scroll position is read from (`onScrolledChange`). `"self"`
|
|
@@ -92771,20 +93065,33 @@ const Table = props => {
|
|
|
92771
93065
|
columns,
|
|
92772
93066
|
canChangeColumnOrder: Boolean(onColumnOrderChange)
|
|
92773
93067
|
});
|
|
93068
|
+
|
|
93069
|
+
// Edition starts from a cell holding the focus itself. Once started, the
|
|
93070
|
+
// focus is in the cell's field and its keys are the field's: an Enter there
|
|
93071
|
+
// validates, it must not start edition again.
|
|
93072
|
+
const canStartEditing = () => dragContextValue.grabTarget === null && document.activeElement.tagName === "TD";
|
|
93073
|
+
// The key starting edition is its first keystroke, typed by Editable
|
|
93074
|
+
// (initialValue). Its default is prevented: the browser would type it a
|
|
93075
|
+
// second time into the field that takes the focus during this keydown.
|
|
93076
|
+
const startEditingWithKey = e => {
|
|
93077
|
+
e.preventDefault();
|
|
93078
|
+
document.activeElement.dispatchEvent(new CustomEvent("editrequested", {
|
|
93079
|
+
bubbles: false,
|
|
93080
|
+
detail: {
|
|
93081
|
+
initialValue: e.key
|
|
93082
|
+
}
|
|
93083
|
+
}));
|
|
93084
|
+
return true;
|
|
93085
|
+
};
|
|
92774
93086
|
useKeyboardShortcuts(ref, [...createSelectionKeyboardShortcuts(selectionController, {
|
|
92775
93087
|
toggleEnabled: true,
|
|
92776
93088
|
enabled: () => dragContextValue.grabTarget === null
|
|
92777
93089
|
}), {
|
|
92778
93090
|
key: "enter",
|
|
92779
93091
|
description: "Edit table cell content",
|
|
92780
|
-
enabled:
|
|
93092
|
+
enabled: canStartEditing,
|
|
92781
93093
|
handler: () => {
|
|
92782
|
-
|
|
92783
|
-
const activeCell = document.activeElement.closest("td");
|
|
92784
|
-
if (!activeCell) {
|
|
92785
|
-
return false;
|
|
92786
|
-
}
|
|
92787
|
-
activeCell.dispatchEvent(new CustomEvent("editrequested", {
|
|
93094
|
+
document.activeElement.dispatchEvent(new CustomEvent("editrequested", {
|
|
92788
93095
|
bubbles: false
|
|
92789
93096
|
}));
|
|
92790
93097
|
return true;
|
|
@@ -92792,37 +93099,13 @@ const Table = props => {
|
|
|
92792
93099
|
}, {
|
|
92793
93100
|
key: "a-z",
|
|
92794
93101
|
description: "Start editing table cell content",
|
|
92795
|
-
enabled:
|
|
92796
|
-
handler:
|
|
92797
|
-
const activeCell = document.activeElement.closest("td");
|
|
92798
|
-
if (!activeCell) {
|
|
92799
|
-
return false;
|
|
92800
|
-
}
|
|
92801
|
-
activeCell.dispatchEvent(new CustomEvent("editrequested", {
|
|
92802
|
-
bubbles: false,
|
|
92803
|
-
detail: {
|
|
92804
|
-
initialValue: e.key
|
|
92805
|
-
}
|
|
92806
|
-
}));
|
|
92807
|
-
return true;
|
|
92808
|
-
}
|
|
93102
|
+
enabled: canStartEditing,
|
|
93103
|
+
handler: startEditingWithKey
|
|
92809
93104
|
}, {
|
|
92810
93105
|
key: "0-9",
|
|
92811
93106
|
description: "Start editing table cell content",
|
|
92812
|
-
enabled:
|
|
92813
|
-
handler:
|
|
92814
|
-
const activeCell = document.activeElement.closest("td");
|
|
92815
|
-
if (!activeCell) {
|
|
92816
|
-
return false;
|
|
92817
|
-
}
|
|
92818
|
-
activeCell.dispatchEvent(new CustomEvent("editrequested", {
|
|
92819
|
-
bubbles: false,
|
|
92820
|
-
detail: {
|
|
92821
|
-
initialValue: e.key
|
|
92822
|
-
}
|
|
92823
|
-
}));
|
|
92824
|
-
return true;
|
|
92825
|
-
}
|
|
93107
|
+
enabled: canStartEditing,
|
|
93108
|
+
handler: startEditingWithKey
|
|
92826
93109
|
}]);
|
|
92827
93110
|
|
|
92828
93111
|
// resizing
|