@jsenv/navi 0.29.476 → 0.29.478
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 +242 -44
- package/dist/dev/jsenv_navi.js.map +13 -11
- package/dist/jsenv_navi.js +242 -44
- package/dist/jsenv_navi.js.map +13 -11
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -6566,11 +6566,6 @@ const CONTROL_PROP_SET = new Set([
|
|
|
6566
6566
|
"autoFocusVisible",
|
|
6567
6567
|
"autoFocusSelect",
|
|
6568
6568
|
|
|
6569
|
-
"onMouseDown",
|
|
6570
|
-
"onClick",
|
|
6571
|
-
"onKeyDown",
|
|
6572
|
-
"onPaste",
|
|
6573
|
-
"onInput",
|
|
6574
6569
|
"eventReactionDefinitions",
|
|
6575
6570
|
|
|
6576
6571
|
"onCancel",
|
|
@@ -12305,7 +12300,7 @@ const putBack = (restoration) => {
|
|
|
12305
12300
|
// The page is not whole yet: the last release puts it back.
|
|
12306
12301
|
return;
|
|
12307
12302
|
}
|
|
12308
|
-
if (
|
|
12303
|
+
if (isDocumentHeld()) {
|
|
12309
12304
|
pendingRestoration = null;
|
|
12310
12305
|
return;
|
|
12311
12306
|
}
|
|
@@ -12321,20 +12316,33 @@ const putBack = (restoration) => {
|
|
|
12321
12316
|
// height, so the same pixels now fall on other rows. Put back after the list
|
|
12322
12317
|
// has placed itself, they would undo it, in the very picture a route transition
|
|
12323
12318
|
// takes. So the list holds the document while it is placing itself, and the
|
|
12324
|
-
// url's offset is not put back meanwhile.
|
|
12325
|
-
// document.
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12319
|
+
// url's offset is not put back meanwhile. One hold per list: two lists can
|
|
12320
|
+
// share one document.
|
|
12321
|
+
//
|
|
12322
|
+
// A hold is for the page its list is on, and mounted is not shown: a page kept
|
|
12323
|
+
// while its route transition plays still has its list when the page returned to
|
|
12324
|
+
// renders, and that list says nothing about where the page arriving goes. So a
|
|
12325
|
+
// hold is taken with the list's page (RoutePageContext), asked whether it is
|
|
12326
|
+
// shown when a position is due — `null` outside any route container: that list
|
|
12327
|
+
// is on whatever page is shown. Not a url: which page an address shows is the
|
|
12328
|
+
// routes' to say, and one page can be shown under several (its params moving
|
|
12329
|
+
// under it).
|
|
12330
|
+
const documentHolds = new Set();
|
|
12331
|
+
const holdDocumentScroll = (routePage) => {
|
|
12332
|
+
const hold = { routePage };
|
|
12333
|
+
documentHolds.add(hold);
|
|
12330
12334
|
return () => {
|
|
12331
|
-
|
|
12332
|
-
return;
|
|
12333
|
-
}
|
|
12334
|
-
released = true;
|
|
12335
|
-
documentHoldCount--;
|
|
12335
|
+
documentHolds.delete(hold);
|
|
12336
12336
|
};
|
|
12337
12337
|
};
|
|
12338
|
+
const isDocumentHeld = () => {
|
|
12339
|
+
for (const { routePage } of documentHolds) {
|
|
12340
|
+
if (routePage === null || routePage.isShown()) {
|
|
12341
|
+
return true;
|
|
12342
|
+
}
|
|
12343
|
+
}
|
|
12344
|
+
return false;
|
|
12345
|
+
};
|
|
12338
12346
|
|
|
12339
12347
|
// What a page holds instead: not the place, which stays the url's, but the
|
|
12340
12348
|
// moment it is put back (see useHoldScrollRestoration). Counted: several parts
|
|
@@ -12592,9 +12600,9 @@ const restoreScrollPosition = (url) => {
|
|
|
12592
12600
|
if (!position) {
|
|
12593
12601
|
return false;
|
|
12594
12602
|
}
|
|
12595
|
-
if (
|
|
12596
|
-
// There is a place to go back to, and the list holding the document
|
|
12597
|
-
// putting it back (see holdDocumentScroll).
|
|
12603
|
+
if (isDocumentHeld()) {
|
|
12604
|
+
// There is a place to go back to, and the list holding the document on
|
|
12605
|
+
// that page is putting it back (see holdDocumentScroll).
|
|
12598
12606
|
return true;
|
|
12599
12607
|
}
|
|
12600
12608
|
scrollTo(position);
|
|
@@ -46903,6 +46911,9 @@ const ControlgroupChildrenWrapper = ({
|
|
|
46903
46911
|
})
|
|
46904
46912
|
});
|
|
46905
46913
|
|
|
46914
|
+
// What the caller writes for these is called from the control's own reactions
|
|
46915
|
+
// (see `reactions` below), so the split leaves them out of both sides.
|
|
46916
|
+
const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyDown", "onPaste", "onInput"]);
|
|
46906
46917
|
/**
|
|
46907
46918
|
* Core hook for interactive field components (InputTextual, InputCheckbox, etc.).
|
|
46908
46919
|
*
|
|
@@ -46917,12 +46928,22 @@ const ControlgroupChildrenWrapper = ({
|
|
|
46917
46928
|
* All state changes route through DOM events on the field element so that
|
|
46918
46929
|
* external subscribers (e.g. useControlUIState, Selectable) receive every update.
|
|
46919
46930
|
*
|
|
46931
|
+
* @param {object} options
|
|
46932
|
+
* @param {"host"|"root"} [options.listenersOn] Where the caller's DOM listeners
|
|
46933
|
+
* (`onPointerDown`, `onFocus`…, never navi's own `onnavi_*`) land. `"host"`,
|
|
46934
|
+
* the default, is right wherever the host is what the person presses and
|
|
46935
|
+
* focuses: the field inside its box, or one element for both. `"root"` is for
|
|
46936
|
+
* a control whose host is a hidden carrier, or whose root also holds what
|
|
46937
|
+
* the control opens, so that a listener hears the control as a whole.
|
|
46938
|
+
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
46939
|
+
* host's either way: the control's own reactions to them call them.
|
|
46920
46940
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
46921
46941
|
*/
|
|
46922
46942
|
const useControlProps = (props, {
|
|
46923
46943
|
controlType,
|
|
46924
46944
|
persists,
|
|
46925
|
-
uiActionInternal
|
|
46945
|
+
uiActionInternal,
|
|
46946
|
+
listenersOn = "host"
|
|
46926
46947
|
}) => {
|
|
46927
46948
|
const debugUIState = useDebugUIState();
|
|
46928
46949
|
const debugAction = useDebugAction();
|
|
@@ -47009,7 +47030,9 @@ const useControlProps = (props, {
|
|
|
47009
47030
|
const [controlRootProps, controlHostProps] = useInteractiveProps(props, {
|
|
47010
47031
|
uiStateController,
|
|
47011
47032
|
boundAction,
|
|
47012
|
-
controlInfo
|
|
47033
|
+
controlInfo,
|
|
47034
|
+
listenersOn,
|
|
47035
|
+
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
47013
47036
|
});
|
|
47014
47037
|
{
|
|
47015
47038
|
const debugInteraction = useDebugInteraction();
|
|
@@ -48062,7 +48085,9 @@ const useControlgroupProps = (props, {
|
|
|
48062
48085
|
wantRequesterButtonState,
|
|
48063
48086
|
uiActionInternal,
|
|
48064
48087
|
allowCapture = false,
|
|
48065
|
-
cascadeValidationToChildren = false
|
|
48088
|
+
cascadeValidationToChildren = false,
|
|
48089
|
+
// See useControlProps.
|
|
48090
|
+
listenersOn = "host"
|
|
48066
48091
|
}) => {
|
|
48067
48092
|
const {
|
|
48068
48093
|
action,
|
|
@@ -48116,7 +48141,8 @@ const useControlgroupProps = (props, {
|
|
|
48116
48141
|
// so we don't have a value concept, nor readonly etc
|
|
48117
48142
|
controlInfo: {
|
|
48118
48143
|
controlType
|
|
48119
|
-
}
|
|
48144
|
+
},
|
|
48145
|
+
listenersOn
|
|
48120
48146
|
});
|
|
48121
48147
|
const {
|
|
48122
48148
|
basePseudoState
|
|
@@ -48255,12 +48281,17 @@ const ControlFacadeChildrenWrapper = ({
|
|
|
48255
48281
|
const useInteractiveProps = (props, {
|
|
48256
48282
|
uiStateController,
|
|
48257
48283
|
boundAction,
|
|
48258
|
-
controlInfo
|
|
48284
|
+
controlInfo,
|
|
48285
|
+
listenersOn,
|
|
48286
|
+
listenersCalledByReactions
|
|
48259
48287
|
}) => {
|
|
48260
48288
|
const {
|
|
48261
48289
|
ref
|
|
48262
48290
|
} = props;
|
|
48263
|
-
const [controlRootProps, controlHostProps] = splitControlProps(props
|
|
48291
|
+
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
48292
|
+
listenersOn,
|
|
48293
|
+
listenersCalledByReactions
|
|
48294
|
+
});
|
|
48264
48295
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
48265
48296
|
if (props.selfInteractions) {
|
|
48266
48297
|
// One attribute, in the DOM, because that is where the claim is read from —
|
|
@@ -48637,8 +48668,10 @@ const useInteractiveProps = (props, {
|
|
|
48637
48668
|
}
|
|
48638
48669
|
}
|
|
48639
48670
|
};
|
|
48671
|
+
const onFocusFromProps = controlHostProps.onFocus;
|
|
48640
48672
|
Object.assign(controlHostProps, {
|
|
48641
48673
|
onFocus: e => {
|
|
48674
|
+
onFocusFromProps?.(e);
|
|
48642
48675
|
// Transfer programmatic focus to the delegate target (navi-focus-delegate or navi-control-proxy-for)
|
|
48643
48676
|
const focusProxyTarget = findFocusDelegateTarget(e.currentTarget) || findControlProxyTarget(e.currentTarget);
|
|
48644
48677
|
if (focusProxyTarget) {
|
|
@@ -48870,7 +48903,10 @@ const useInteractiveProps = (props, {
|
|
|
48870
48903
|
}
|
|
48871
48904
|
return [controlRootProps, controlHostProps];
|
|
48872
48905
|
};
|
|
48873
|
-
const splitControlProps = props
|
|
48906
|
+
const splitControlProps = (props, {
|
|
48907
|
+
listenersOn,
|
|
48908
|
+
listenersCalledByReactions
|
|
48909
|
+
}) => {
|
|
48874
48910
|
const {
|
|
48875
48911
|
ref
|
|
48876
48912
|
} = props;
|
|
@@ -48887,10 +48923,20 @@ const splitControlProps = props => {
|
|
|
48887
48923
|
if (isControlProp(key)) {
|
|
48888
48924
|
continue;
|
|
48889
48925
|
}
|
|
48926
|
+
if (listenersCalledByReactions?.has(key)) {
|
|
48927
|
+
continue;
|
|
48928
|
+
}
|
|
48929
|
+
if (listenersOn === "host" && isDomListenerProp(key)) {
|
|
48930
|
+
controlHostProps[key] = props[key];
|
|
48931
|
+
continue;
|
|
48932
|
+
}
|
|
48890
48933
|
controlRootProps[key] = props[key];
|
|
48891
48934
|
}
|
|
48892
48935
|
return [controlRootProps, controlHostProps];
|
|
48893
48936
|
};
|
|
48937
|
+
// `onPointerDown`, `onFocusOut`… but not navi's own events (`onnavi_command`),
|
|
48938
|
+
// which keep the element they are dispatched on.
|
|
48939
|
+
const isDomListenerProp = key => /^on[A-Z]/.test(key);
|
|
48894
48940
|
const isCheckableInput = (controlType, typeProp) => controlType === "input" && (typeProp === "radio" || typeProp === "checkbox");
|
|
48895
48941
|
|
|
48896
48942
|
// The labels the DOM itself can hand over: a wrapping <label>, or a label[for]
|
|
@@ -61469,6 +61515,86 @@ const useAutoSelectReadOnly = (props) => {
|
|
|
61469
61515
|
return { onFocus, onMouseDown, onPointerDown };
|
|
61470
61516
|
};
|
|
61471
61517
|
|
|
61518
|
+
// selectOnPress: a field whose value is usually replaced rather than edited (an
|
|
61519
|
+
// address, a place to search) behaves like the address bar of a browser. The
|
|
61520
|
+
// first gesture that engages it after it took the focus selects its whole text,
|
|
61521
|
+
// so typing replaces it; a second press places the caret, to fix one part.
|
|
61522
|
+
//
|
|
61523
|
+
// Engaged by a gesture, never by the focus itself: a dialog opening hands the
|
|
61524
|
+
// focus to its first field (docs/autofocus.md), and a field selected on focus
|
|
61525
|
+
// would greet whoever opened the dialog already half-way into an edit. So a
|
|
61526
|
+
// focus that came without a press waits for the first key that types.
|
|
61527
|
+
//
|
|
61528
|
+
// The press selects once it is over, on `click`: the browser places the caret
|
|
61529
|
+
// on mousedown and may collapse a selection on mouseup, so anything selected
|
|
61530
|
+
// before the release is undone by it. `mousedown` decides whether a press
|
|
61531
|
+
// engages because a finger only produces one after a tap — a finger that
|
|
61532
|
+
// scrolled the page never reaches the field. Touch is included on purpose (see
|
|
61533
|
+
// use_autoselect_read_only.js for why a read-only field leaves it out): on a
|
|
61534
|
+
// field meant to be replaced, the native selection handles are expected.
|
|
61535
|
+
const useSelectOnPress = (props, selectOnPress) => {
|
|
61536
|
+
const engagedRef = useRef(false);
|
|
61537
|
+
const pressEngagesRef = useRef(false);
|
|
61538
|
+
|
|
61539
|
+
if (!selectOnPress) {
|
|
61540
|
+
return null;
|
|
61541
|
+
}
|
|
61542
|
+
const onMouseDown = (e) => {
|
|
61543
|
+
props.onMouseDown(e);
|
|
61544
|
+
if (e.defaultPrevented) {
|
|
61545
|
+
return;
|
|
61546
|
+
}
|
|
61547
|
+
if (e.button !== 0) {
|
|
61548
|
+
return;
|
|
61549
|
+
}
|
|
61550
|
+
pressEngagesRef.current = !engagedRef.current;
|
|
61551
|
+
engagedRef.current = true;
|
|
61552
|
+
};
|
|
61553
|
+
const onClick = (e) => {
|
|
61554
|
+
props.onClick(e);
|
|
61555
|
+
if (!pressEngagesRef.current) {
|
|
61556
|
+
return;
|
|
61557
|
+
}
|
|
61558
|
+
pressEngagesRef.current = false;
|
|
61559
|
+
if (e.defaultPrevented) {
|
|
61560
|
+
return;
|
|
61561
|
+
}
|
|
61562
|
+
const input = e.currentTarget;
|
|
61563
|
+
if (input.selectionStart !== input.selectionEnd) {
|
|
61564
|
+
// The press dragged across a part of the text: that part is what was meant.
|
|
61565
|
+
return;
|
|
61566
|
+
}
|
|
61567
|
+
input.select();
|
|
61568
|
+
// Keep the beginning of the text visible instead of scrolling to the end
|
|
61569
|
+
input.scrollLeft = 0;
|
|
61570
|
+
};
|
|
61571
|
+
const onKeyDown = (e) => {
|
|
61572
|
+
if (!engagedRef.current) {
|
|
61573
|
+
const defaultAction = getKeyboardEventDefaultAction(e);
|
|
61574
|
+
if (defaultAction === "type") {
|
|
61575
|
+
engagedRef.current = true;
|
|
61576
|
+
// Before the control's own keydown: maxLengthGuard measures the room
|
|
61577
|
+
// left against the selection the key is about to replace.
|
|
61578
|
+
e.currentTarget.select();
|
|
61579
|
+
} else if (defaultAction === "cursor_move") {
|
|
61580
|
+
engagedRef.current = true;
|
|
61581
|
+
}
|
|
61582
|
+
}
|
|
61583
|
+
props.onKeyDown(e);
|
|
61584
|
+
};
|
|
61585
|
+
const onBlur = (e) => {
|
|
61586
|
+
props.onBlur(e);
|
|
61587
|
+
if (document.activeElement === e.currentTarget) {
|
|
61588
|
+
// The window went to the background and the field still holds the focus:
|
|
61589
|
+
// coming back is not a new arrival.
|
|
61590
|
+
return;
|
|
61591
|
+
}
|
|
61592
|
+
engagedRef.current = false;
|
|
61593
|
+
};
|
|
61594
|
+
|
|
61595
|
+
return { onMouseDown, onClick, onKeyDown, onBlur };
|
|
61596
|
+
};
|
|
61597
|
+
|
|
61472
61598
|
/**
|
|
61473
61599
|
* Input component for all textual input types.
|
|
61474
61600
|
*
|
|
@@ -61576,7 +61702,8 @@ const InputTextualUI = props => {
|
|
|
61576
61702
|
ui,
|
|
61577
61703
|
variant,
|
|
61578
61704
|
backgroundColor,
|
|
61579
|
-
width = "maxLength"
|
|
61705
|
+
width = "maxLength",
|
|
61706
|
+
selectOnPress
|
|
61580
61707
|
} = props;
|
|
61581
61708
|
const [inputControlRootProps, inputControlHostProps, controlChildrenWrapperProps] = useInputTextualProps(props);
|
|
61582
61709
|
const {
|
|
@@ -61618,6 +61745,8 @@ const InputTextualUI = props => {
|
|
|
61618
61745
|
} else {
|
|
61619
61746
|
inputControlHostProps.width = width;
|
|
61620
61747
|
}
|
|
61748
|
+
delete inputControlRootProps.selectOnPress;
|
|
61749
|
+
inputControlHostProps.selectOnPress = selectOnPress;
|
|
61621
61750
|
return jsxs(Box, {
|
|
61622
61751
|
as: "span",
|
|
61623
61752
|
inline: true,
|
|
@@ -61690,7 +61819,7 @@ const resolveWidthFromMaxLength = (maxLength, inputMode) => {
|
|
|
61690
61819
|
// rest — the guards, the slots, the constraints — which describe an edition
|
|
61691
61820
|
// that does not happen here. What survives is what a Box understands: width,
|
|
61692
61821
|
// spacing, colors, className, style.
|
|
61693
|
-
const INPUT_ONLY_PROPS = ["value", "defaultValue", "signal", "id", "maxLength", "inputMode", "width", "variant", "type", "name", "placeholder", "required", "readOnly", "disabled", "loading", "error", "min", "max", "step", "pattern", "autoComplete", "autoCorrect", "spellcheck", "charGuard", "maxLengthGuard", "list", "suggestions", "headless", "fieldSizing", "action", "uiAction", "ui", "children"];
|
|
61822
|
+
const INPUT_ONLY_PROPS = ["value", "defaultValue", "signal", "id", "maxLength", "inputMode", "width", "variant", "type", "name", "placeholder", "required", "readOnly", "disabled", "loading", "error", "min", "max", "step", "pattern", "autoComplete", "autoCorrect", "spellcheck", "charGuard", "maxLengthGuard", "list", "suggestions", "headless", "fieldSizing", "selectOnPress", "action", "uiAction", "ui", "children"];
|
|
61694
61823
|
const InputTextualAsText = props => {
|
|
61695
61824
|
installInputCss();
|
|
61696
61825
|
// The id a Field handed down, which is what its Label points at.
|
|
@@ -61754,14 +61883,20 @@ const InputTextualFirstResolver = props => {
|
|
|
61754
61883
|
const InputTextual = /*#__PURE__*/createComponentResolver([InputTextualAsTextResolver, InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
|
|
61755
61884
|
const RealInput = ({
|
|
61756
61885
|
maxLength,
|
|
61886
|
+
selectOnPress,
|
|
61757
61887
|
...domProps
|
|
61758
61888
|
}) => {
|
|
61759
61889
|
const autoSelectReadOnlyProps = useAutoSelectReadOnly(domProps);
|
|
61890
|
+
const selectOnPressProps = useSelectOnPress({
|
|
61891
|
+
...domProps,
|
|
61892
|
+
...autoSelectReadOnlyProps
|
|
61893
|
+
}, selectOnPress);
|
|
61760
61894
|
return jsx(Box, {
|
|
61761
61895
|
...domProps,
|
|
61762
61896
|
as: "input",
|
|
61763
61897
|
baseClassName: "navi_control_input",
|
|
61764
61898
|
...autoSelectReadOnlyProps,
|
|
61899
|
+
...selectOnPressProps,
|
|
61765
61900
|
// Never set native maxLength — our guard handles it. Omitting it entirely
|
|
61766
61901
|
// avoids a Preact quirk: setting maxLength={undefined} on a fresh DOM element
|
|
61767
61902
|
// (e.g. after a Suspense remount) causes Preact to run `el.maxLength = ""`
|
|
@@ -77592,6 +77727,11 @@ const ListItemSelectable = props => {
|
|
|
77592
77727
|
...(hasSelectedProp ? {
|
|
77593
77728
|
checked: selected
|
|
77594
77729
|
} : null)
|
|
77730
|
+
},
|
|
77731
|
+
// The host is the visually hidden input carrying the selection: the row
|
|
77732
|
+
// is what the person sees and presses (see onClick above).
|
|
77733
|
+
{
|
|
77734
|
+
listenersOn: "root"
|
|
77595
77735
|
});
|
|
77596
77736
|
const {
|
|
77597
77737
|
checked,
|
|
@@ -79388,6 +79528,19 @@ const useListScrollSync = ({
|
|
|
79388
79528
|
// eslint-disable-next-line no-unused-expressions
|
|
79389
79529
|
virtualItemSizeSignal.value;
|
|
79390
79530
|
}
|
|
79531
|
+
// The hold ending — the user reaching for the list, something else moving
|
|
79532
|
+
// it, the item it opens on never coming — ends its hold on the document with
|
|
79533
|
+
// it (see holdDocumentScroll), rather than at the next render, which nothing
|
|
79534
|
+
// owes: an offset put back on this page meanwhile is the reader's.
|
|
79535
|
+
const releaseDocumentHoldRef = useRef(null);
|
|
79536
|
+
const letGo = () => {
|
|
79537
|
+
startPlaceRef.current.userTookOver = true;
|
|
79538
|
+
const releaseDocumentHold = releaseDocumentHoldRef.current;
|
|
79539
|
+
if (releaseDocumentHold) {
|
|
79540
|
+
releaseDocumentHoldRef.current = null;
|
|
79541
|
+
releaseDocumentHold();
|
|
79542
|
+
}
|
|
79543
|
+
};
|
|
79391
79544
|
// Set around a scroll the list performs itself. What it protects against is
|
|
79392
79545
|
// not the scroll event as such, but what the listener would conclude from it:
|
|
79393
79546
|
// the position it is about to read was chosen to keep the items where they
|
|
@@ -79622,7 +79775,7 @@ const useListScrollSync = ({
|
|
|
79622
79775
|
}
|
|
79623
79776
|
openAt = scrolledFallback;
|
|
79624
79777
|
if (openAt === "start" || openAt === undefined) {
|
|
79625
|
-
|
|
79778
|
+
letGo();
|
|
79626
79779
|
return;
|
|
79627
79780
|
}
|
|
79628
79781
|
} else {
|
|
@@ -79774,13 +79927,39 @@ const useListScrollSync = ({
|
|
|
79774
79927
|
}, []);
|
|
79775
79928
|
// Held on an item of a list scrolling the document: where the document goes is
|
|
79776
79929
|
// this list's to say, measured on the item, not the url's offset in pixels
|
|
79777
|
-
// (see holdDocumentScroll).
|
|
79930
|
+
// (see holdDocumentScroll). The hold read again: a layout effect of this
|
|
79931
|
+
// commit may have let go already (see placeWhereHeld).
|
|
79932
|
+
const routePage = useContext(RoutePageContext);
|
|
79778
79933
|
useLayoutEffect(() => {
|
|
79779
|
-
if (!heldSomewhere || getScroller() !== document.scrollingElement) {
|
|
79934
|
+
if (!heldSomewhere || startPlaceRef.current.userTookOver || getScroller() !== document.scrollingElement) {
|
|
79780
79935
|
return undefined;
|
|
79781
79936
|
}
|
|
79782
|
-
|
|
79783
|
-
|
|
79937
|
+
const releaseDocumentHold = holdDocumentScroll(routePage);
|
|
79938
|
+
releaseDocumentHoldRef.current = releaseDocumentHold;
|
|
79939
|
+
return () => {
|
|
79940
|
+
releaseDocumentHoldRef.current = null;
|
|
79941
|
+
releaseDocumentHold();
|
|
79942
|
+
};
|
|
79943
|
+
}, [heldSomewhere, scrolledWanted, scrollerElResolved]);
|
|
79944
|
+
// A page kept while its route transition plays is hidden until it is taken
|
|
79945
|
+
// down: nothing of the list can be measured there any more, and its scroller
|
|
79946
|
+
// shows the page arriving (see route_page_context.js). So the list lets go of
|
|
79947
|
+
// it, and stops answering its resizes and scrolls: measured hidden, every
|
|
79948
|
+
// item stands at 0×0, and the top item put back where it was would scroll
|
|
79949
|
+
// the page arriving by that item's offset. Kept mounted on purpose, so the
|
|
79950
|
+
// listeners stay; what they would do is what ends.
|
|
79951
|
+
const pageLeftRef = useRef(false);
|
|
79952
|
+
useLayoutEffect(() => {
|
|
79953
|
+
if (!routePage) {
|
|
79954
|
+
return undefined;
|
|
79955
|
+
}
|
|
79956
|
+
return routePage.leavingSignal.subscribe(leaving => {
|
|
79957
|
+
if (leaving) {
|
|
79958
|
+
pageLeftRef.current = true;
|
|
79959
|
+
letGo();
|
|
79960
|
+
}
|
|
79961
|
+
});
|
|
79962
|
+
}, []);
|
|
79784
79963
|
// What to do when the list's own geometry moves under it, kept fresh for the
|
|
79785
79964
|
// observer below (which is installed once).
|
|
79786
79965
|
const onGeometryChangeRef = useRef(null);
|
|
@@ -79814,18 +79993,15 @@ const useListScrollSync = ({
|
|
|
79814
79993
|
return undefined;
|
|
79815
79994
|
}
|
|
79816
79995
|
const scrollerEl = getScroller();
|
|
79817
|
-
const takeOver = () => {
|
|
79818
|
-
startPlaceRef.current.userTookOver = true;
|
|
79819
|
-
};
|
|
79820
79996
|
const takeOverEvents = ["wheel", "touchstart", "pointerdown", "keydown"];
|
|
79821
79997
|
for (const type of takeOverEvents) {
|
|
79822
|
-
scrollerEl.addEventListener(type,
|
|
79998
|
+
scrollerEl.addEventListener(type, letGo, {
|
|
79823
79999
|
passive: true
|
|
79824
80000
|
});
|
|
79825
80001
|
}
|
|
79826
80002
|
return () => {
|
|
79827
80003
|
for (const type of takeOverEvents) {
|
|
79828
|
-
scrollerEl.removeEventListener(type,
|
|
80004
|
+
scrollerEl.removeEventListener(type, letGo);
|
|
79829
80005
|
}
|
|
79830
80006
|
};
|
|
79831
80007
|
}, [scrolledWanted, scrollerElResolved]);
|
|
@@ -79983,6 +80159,9 @@ const useListScrollSync = ({
|
|
|
79983
80159
|
}
|
|
79984
80160
|
};
|
|
79985
80161
|
const observer = new ResizeObserver(entries => {
|
|
80162
|
+
if (pageLeftRef.current) {
|
|
80163
|
+
return;
|
|
80164
|
+
}
|
|
79986
80165
|
// A box that grows may be a box that starts to scroll — the scroller is
|
|
79987
80166
|
// resolved again before anything is done about the resize, so that a
|
|
79988
80167
|
// list which has outgrown a bounded ancestor stops holding on to the
|
|
@@ -80396,6 +80575,9 @@ const useListScrollSync = ({
|
|
|
80396
80575
|
}
|
|
80397
80576
|
const scrollerEl = getScroller();
|
|
80398
80577
|
const onScroll = () => {
|
|
80578
|
+
if (pageLeftRef.current) {
|
|
80579
|
+
return;
|
|
80580
|
+
}
|
|
80399
80581
|
// Which way the user is going, read before the position is stored: what
|
|
80400
80582
|
// the window keeps ahead is ahead of this.
|
|
80401
80583
|
const previousScroll = currentScrollRef.current;
|
|
@@ -80453,7 +80635,7 @@ const useListScrollSync = ({
|
|
|
80453
80635
|
const low = way.from < way.to ? way.from : way.to;
|
|
80454
80636
|
const high = way.from < way.to ? way.to : way.from;
|
|
80455
80637
|
if (position < low - 1 || position > high + 1) {
|
|
80456
|
-
|
|
80638
|
+
letGo();
|
|
80457
80639
|
}
|
|
80458
80640
|
};
|
|
80459
80641
|
// A page-level scroller does not emit "scroll" on the element itself
|
|
@@ -86530,7 +86712,11 @@ const PickerButton = props => {
|
|
|
86530
86712
|
...props,
|
|
86531
86713
|
ref: inputRef
|
|
86532
86714
|
}, {
|
|
86533
|
-
controlType: "picker"
|
|
86715
|
+
controlType: "picker",
|
|
86716
|
+
// The root holds what the picker opens as well as its trigger: a
|
|
86717
|
+
// listener on the picker hears what bubbles out of the popup (the focus
|
|
86718
|
+
// leaving it, see picker_custom.jsx).
|
|
86719
|
+
listenersOn: "root"
|
|
86534
86720
|
});
|
|
86535
86721
|
const uiStateController = getUIStateControllerById(inputProps.id);
|
|
86536
86722
|
const value = uiStateController.uiState;
|
|
@@ -89739,6 +89925,8 @@ const InputDuration = props => {
|
|
|
89739
89925
|
}, {
|
|
89740
89926
|
controlType: "duration_group",
|
|
89741
89927
|
cascadeValidationToChildren: true,
|
|
89928
|
+
// The host is an <input type="hidden"> carrying the value.
|
|
89929
|
+
listenersOn: "root",
|
|
89742
89930
|
// Aggregates sub-input values into an ISO 8601-like duration string.
|
|
89743
89931
|
// durationToISOString preserves non-numeric mid-edit values
|
|
89744
89932
|
// (e.g. hours="ab") between their unit markers ("PTabH30M"),
|
|
@@ -89805,7 +89993,7 @@ const InputDuration = props => {
|
|
|
89805
89993
|
} = groupHostProps;
|
|
89806
89994
|
const groupRef = useRef();
|
|
89807
89995
|
useInputGroup(groupRef);
|
|
89808
|
-
const clipboardProps = useClipboardProps(groupRef);
|
|
89996
|
+
const clipboardProps = useClipboardProps(groupRef, groupRootProps);
|
|
89809
89997
|
const hourValue = components?.hours;
|
|
89810
89998
|
const minuteValue = components?.minutes;
|
|
89811
89999
|
const rawSecondValue = components?.seconds;
|
|
@@ -89878,7 +90066,11 @@ const InputDuration = props => {
|
|
|
89878
90066
|
const getVisibleFields = container => {
|
|
89879
90067
|
return ["hour", "minute", "second", "millisecond"].filter(f => container.querySelector(`[navi-input-type="${f}"]`) !== null);
|
|
89880
90068
|
};
|
|
89881
|
-
|
|
90069
|
+
|
|
90070
|
+
// Spread after the group's props, so the caller's listeners for these events
|
|
90071
|
+
// are called from here. Whatever they do, the group's own reading runs too, as
|
|
90072
|
+
// it does after a field's paste guard prevented the paste.
|
|
90073
|
+
const useClipboardProps = (groupRef, callerProps) => {
|
|
89882
90074
|
const getClipboardPayload = () => {
|
|
89883
90075
|
const groupEl = groupRef.current;
|
|
89884
90076
|
if (!groupEl) {
|
|
@@ -89944,6 +90136,7 @@ const useClipboardProps = groupRef => {
|
|
|
89944
90136
|
return true;
|
|
89945
90137
|
};
|
|
89946
90138
|
const onCopy = e => {
|
|
90139
|
+
callerProps.onCopy?.(e);
|
|
89947
90140
|
if (!isFromDurationField(e)) {
|
|
89948
90141
|
return;
|
|
89949
90142
|
}
|
|
@@ -89956,6 +90149,7 @@ const useClipboardProps = groupRef => {
|
|
|
89956
90149
|
e.preventDefault();
|
|
89957
90150
|
};
|
|
89958
90151
|
const onCut = e => {
|
|
90152
|
+
callerProps.onCut?.(e);
|
|
89959
90153
|
if (!isFromDurationField(e)) {
|
|
89960
90154
|
return;
|
|
89961
90155
|
}
|
|
@@ -89968,6 +90162,7 @@ const useClipboardProps = groupRef => {
|
|
|
89968
90162
|
applyToGroup("", e);
|
|
89969
90163
|
};
|
|
89970
90164
|
const onPaste = e => {
|
|
90165
|
+
callerProps.onPaste?.(e);
|
|
89971
90166
|
if (!isFromDurationField(e)) {
|
|
89972
90167
|
return;
|
|
89973
90168
|
}
|
|
@@ -92537,7 +92732,10 @@ function WheelUI(props) {
|
|
|
92537
92732
|
// action explicitly on settle. A caller can still override actionEvent.
|
|
92538
92733
|
actionEvent: props.actionEvent ?? "custom"
|
|
92539
92734
|
}, {
|
|
92540
|
-
controlType: "input"
|
|
92735
|
+
controlType: "input",
|
|
92736
|
+
// The host is the hidden input carrying the value: the container is
|
|
92737
|
+
// what is focused and spun.
|
|
92738
|
+
listenersOn: "root"
|
|
92541
92739
|
});
|
|
92542
92740
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
92543
92741
|
const {
|