@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/jsenv_navi.js
CHANGED
|
@@ -6345,11 +6345,6 @@ const CONTROL_PROP_SET = new Set([
|
|
|
6345
6345
|
"autoFocusVisible",
|
|
6346
6346
|
"autoFocusSelect",
|
|
6347
6347
|
|
|
6348
|
-
"onMouseDown",
|
|
6349
|
-
"onClick",
|
|
6350
|
-
"onKeyDown",
|
|
6351
|
-
"onPaste",
|
|
6352
|
-
"onInput",
|
|
6353
6348
|
"eventReactionDefinitions",
|
|
6354
6349
|
|
|
6355
6350
|
"onCancel",
|
|
@@ -11877,7 +11872,7 @@ const putBack = (restoration) => {
|
|
|
11877
11872
|
// The page is not whole yet: the last release puts it back.
|
|
11878
11873
|
return;
|
|
11879
11874
|
}
|
|
11880
|
-
if (
|
|
11875
|
+
if (isDocumentHeld()) {
|
|
11881
11876
|
pendingRestoration = null;
|
|
11882
11877
|
return;
|
|
11883
11878
|
}
|
|
@@ -11893,20 +11888,33 @@ const putBack = (restoration) => {
|
|
|
11893
11888
|
// height, so the same pixels now fall on other rows. Put back after the list
|
|
11894
11889
|
// has placed itself, they would undo it, in the very picture a route transition
|
|
11895
11890
|
// takes. So the list holds the document while it is placing itself, and the
|
|
11896
|
-
// url's offset is not put back meanwhile.
|
|
11897
|
-
// document.
|
|
11898
|
-
|
|
11899
|
-
|
|
11900
|
-
|
|
11901
|
-
|
|
11891
|
+
// url's offset is not put back meanwhile. One hold per list: two lists can
|
|
11892
|
+
// share one document.
|
|
11893
|
+
//
|
|
11894
|
+
// A hold is for the page its list is on, and mounted is not shown: a page kept
|
|
11895
|
+
// while its route transition plays still has its list when the page returned to
|
|
11896
|
+
// renders, and that list says nothing about where the page arriving goes. So a
|
|
11897
|
+
// hold is taken with the list's page (RoutePageContext), asked whether it is
|
|
11898
|
+
// shown when a position is due — `null` outside any route container: that list
|
|
11899
|
+
// is on whatever page is shown. Not a url: which page an address shows is the
|
|
11900
|
+
// routes' to say, and one page can be shown under several (its params moving
|
|
11901
|
+
// under it).
|
|
11902
|
+
const documentHolds = new Set();
|
|
11903
|
+
const holdDocumentScroll = (routePage) => {
|
|
11904
|
+
const hold = { routePage };
|
|
11905
|
+
documentHolds.add(hold);
|
|
11902
11906
|
return () => {
|
|
11903
|
-
|
|
11904
|
-
return;
|
|
11905
|
-
}
|
|
11906
|
-
released = true;
|
|
11907
|
-
documentHoldCount--;
|
|
11907
|
+
documentHolds.delete(hold);
|
|
11908
11908
|
};
|
|
11909
11909
|
};
|
|
11910
|
+
const isDocumentHeld = () => {
|
|
11911
|
+
for (const { routePage } of documentHolds) {
|
|
11912
|
+
if (routePage === null || routePage.isShown()) {
|
|
11913
|
+
return true;
|
|
11914
|
+
}
|
|
11915
|
+
}
|
|
11916
|
+
return false;
|
|
11917
|
+
};
|
|
11910
11918
|
|
|
11911
11919
|
// What a page holds instead: not the place, which stays the url's, but the
|
|
11912
11920
|
// moment it is put back (see useHoldScrollRestoration). Counted: several parts
|
|
@@ -12164,9 +12172,9 @@ const restoreScrollPosition = (url) => {
|
|
|
12164
12172
|
if (!position) {
|
|
12165
12173
|
return false;
|
|
12166
12174
|
}
|
|
12167
|
-
if (
|
|
12168
|
-
// There is a place to go back to, and the list holding the document
|
|
12169
|
-
// putting it back (see holdDocumentScroll).
|
|
12175
|
+
if (isDocumentHeld()) {
|
|
12176
|
+
// There is a place to go back to, and the list holding the document on
|
|
12177
|
+
// that page is putting it back (see holdDocumentScroll).
|
|
12170
12178
|
return true;
|
|
12171
12179
|
}
|
|
12172
12180
|
scrollTo(position);
|
|
@@ -45739,6 +45747,9 @@ const ControlgroupChildrenWrapper = ({
|
|
|
45739
45747
|
})
|
|
45740
45748
|
});
|
|
45741
45749
|
|
|
45750
|
+
// What the caller writes for these is called from the control's own reactions
|
|
45751
|
+
// (see `reactions` below), so the split leaves them out of both sides.
|
|
45752
|
+
const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyDown", "onPaste", "onInput"]);
|
|
45742
45753
|
/**
|
|
45743
45754
|
* Core hook for interactive field components (InputTextual, InputCheckbox, etc.).
|
|
45744
45755
|
*
|
|
@@ -45753,12 +45764,22 @@ const ControlgroupChildrenWrapper = ({
|
|
|
45753
45764
|
* All state changes route through DOM events on the field element so that
|
|
45754
45765
|
* external subscribers (e.g. useControlUIState, Selectable) receive every update.
|
|
45755
45766
|
*
|
|
45767
|
+
* @param {object} options
|
|
45768
|
+
* @param {"host"|"root"} [options.listenersOn] Where the caller's DOM listeners
|
|
45769
|
+
* (`onPointerDown`, `onFocus`…, never navi's own `onnavi_*`) land. `"host"`,
|
|
45770
|
+
* the default, is right wherever the host is what the person presses and
|
|
45771
|
+
* focuses: the field inside its box, or one element for both. `"root"` is for
|
|
45772
|
+
* a control whose host is a hidden carrier, or whose root also holds what
|
|
45773
|
+
* the control opens, so that a listener hears the control as a whole.
|
|
45774
|
+
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
45775
|
+
* host's either way: the control's own reactions to them call them.
|
|
45756
45776
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
45757
45777
|
*/
|
|
45758
45778
|
const useControlProps = (props, {
|
|
45759
45779
|
controlType,
|
|
45760
45780
|
persists,
|
|
45761
|
-
uiActionInternal
|
|
45781
|
+
uiActionInternal,
|
|
45782
|
+
listenersOn = "host"
|
|
45762
45783
|
}) => {
|
|
45763
45784
|
const debugUIState = useDebugUIState();
|
|
45764
45785
|
|
|
@@ -45844,7 +45865,9 @@ const useControlProps = (props, {
|
|
|
45844
45865
|
const [controlRootProps, controlHostProps] = useInteractiveProps(props, {
|
|
45845
45866
|
uiStateController,
|
|
45846
45867
|
boundAction,
|
|
45847
|
-
controlInfo
|
|
45868
|
+
controlInfo,
|
|
45869
|
+
listenersOn,
|
|
45870
|
+
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
45848
45871
|
});
|
|
45849
45872
|
{
|
|
45850
45873
|
const debugInteraction = useDebugInteraction();
|
|
@@ -46878,7 +46901,9 @@ const useControlgroupProps = (props, {
|
|
|
46878
46901
|
wantRequesterButtonState,
|
|
46879
46902
|
uiActionInternal,
|
|
46880
46903
|
allowCapture = false,
|
|
46881
|
-
cascadeValidationToChildren = false
|
|
46904
|
+
cascadeValidationToChildren = false,
|
|
46905
|
+
// See useControlProps.
|
|
46906
|
+
listenersOn = "host"
|
|
46882
46907
|
}) => {
|
|
46883
46908
|
const {
|
|
46884
46909
|
action,
|
|
@@ -46928,7 +46953,8 @@ const useControlgroupProps = (props, {
|
|
|
46928
46953
|
// so we don't have a value concept, nor readonly etc
|
|
46929
46954
|
controlInfo: {
|
|
46930
46955
|
controlType
|
|
46931
|
-
}
|
|
46956
|
+
},
|
|
46957
|
+
listenersOn
|
|
46932
46958
|
});
|
|
46933
46959
|
const {
|
|
46934
46960
|
basePseudoState
|
|
@@ -47067,12 +47093,17 @@ const ControlFacadeChildrenWrapper = ({
|
|
|
47067
47093
|
const useInteractiveProps = (props, {
|
|
47068
47094
|
uiStateController,
|
|
47069
47095
|
boundAction,
|
|
47070
|
-
controlInfo
|
|
47096
|
+
controlInfo,
|
|
47097
|
+
listenersOn,
|
|
47098
|
+
listenersCalledByReactions
|
|
47071
47099
|
}) => {
|
|
47072
47100
|
const {
|
|
47073
47101
|
ref
|
|
47074
47102
|
} = props;
|
|
47075
|
-
const [controlRootProps, controlHostProps] = splitControlProps(props
|
|
47103
|
+
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
47104
|
+
listenersOn,
|
|
47105
|
+
listenersCalledByReactions
|
|
47106
|
+
});
|
|
47076
47107
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
47077
47108
|
if (props.selfInteractions) {
|
|
47078
47109
|
// One attribute, in the DOM, because that is where the claim is read from —
|
|
@@ -47449,8 +47480,10 @@ const useInteractiveProps = (props, {
|
|
|
47449
47480
|
}
|
|
47450
47481
|
}
|
|
47451
47482
|
};
|
|
47483
|
+
const onFocusFromProps = controlHostProps.onFocus;
|
|
47452
47484
|
Object.assign(controlHostProps, {
|
|
47453
47485
|
onFocus: e => {
|
|
47486
|
+
onFocusFromProps?.(e);
|
|
47454
47487
|
// Transfer programmatic focus to the delegate target (navi-focus-delegate or navi-control-proxy-for)
|
|
47455
47488
|
const focusProxyTarget = findFocusDelegateTarget(e.currentTarget) || findControlProxyTarget(e.currentTarget);
|
|
47456
47489
|
if (focusProxyTarget) {
|
|
@@ -47680,7 +47713,10 @@ const useInteractiveProps = (props, {
|
|
|
47680
47713
|
}
|
|
47681
47714
|
return [controlRootProps, controlHostProps];
|
|
47682
47715
|
};
|
|
47683
|
-
const splitControlProps = props
|
|
47716
|
+
const splitControlProps = (props, {
|
|
47717
|
+
listenersOn,
|
|
47718
|
+
listenersCalledByReactions
|
|
47719
|
+
}) => {
|
|
47684
47720
|
const {
|
|
47685
47721
|
ref
|
|
47686
47722
|
} = props;
|
|
@@ -47697,10 +47733,20 @@ const splitControlProps = props => {
|
|
|
47697
47733
|
if (isControlProp(key)) {
|
|
47698
47734
|
continue;
|
|
47699
47735
|
}
|
|
47736
|
+
if (listenersCalledByReactions?.has(key)) {
|
|
47737
|
+
continue;
|
|
47738
|
+
}
|
|
47739
|
+
if (listenersOn === "host" && isDomListenerProp(key)) {
|
|
47740
|
+
controlHostProps[key] = props[key];
|
|
47741
|
+
continue;
|
|
47742
|
+
}
|
|
47700
47743
|
controlRootProps[key] = props[key];
|
|
47701
47744
|
}
|
|
47702
47745
|
return [controlRootProps, controlHostProps];
|
|
47703
47746
|
};
|
|
47747
|
+
// `onPointerDown`, `onFocusOut`… but not navi's own events (`onnavi_command`),
|
|
47748
|
+
// which keep the element they are dispatched on.
|
|
47749
|
+
const isDomListenerProp = key => /^on[A-Z]/.test(key);
|
|
47704
47750
|
const isCheckableInput = (controlType, typeProp) => controlType === "input" && (typeProp === "radio" || typeProp === "checkbox");
|
|
47705
47751
|
|
|
47706
47752
|
// The labels the DOM itself can hand over: a wrapping <label>, or a label[for]
|
|
@@ -60243,6 +60289,86 @@ const useAutoSelectReadOnly = (props) => {
|
|
|
60243
60289
|
return { onFocus, onMouseDown, onPointerDown };
|
|
60244
60290
|
};
|
|
60245
60291
|
|
|
60292
|
+
// selectOnPress: a field whose value is usually replaced rather than edited (an
|
|
60293
|
+
// address, a place to search) behaves like the address bar of a browser. The
|
|
60294
|
+
// first gesture that engages it after it took the focus selects its whole text,
|
|
60295
|
+
// so typing replaces it; a second press places the caret, to fix one part.
|
|
60296
|
+
//
|
|
60297
|
+
// Engaged by a gesture, never by the focus itself: a dialog opening hands the
|
|
60298
|
+
// focus to its first field (docs/autofocus.md), and a field selected on focus
|
|
60299
|
+
// would greet whoever opened the dialog already half-way into an edit. So a
|
|
60300
|
+
// focus that came without a press waits for the first key that types.
|
|
60301
|
+
//
|
|
60302
|
+
// The press selects once it is over, on `click`: the browser places the caret
|
|
60303
|
+
// on mousedown and may collapse a selection on mouseup, so anything selected
|
|
60304
|
+
// before the release is undone by it. `mousedown` decides whether a press
|
|
60305
|
+
// engages because a finger only produces one after a tap — a finger that
|
|
60306
|
+
// scrolled the page never reaches the field. Touch is included on purpose (see
|
|
60307
|
+
// use_autoselect_read_only.js for why a read-only field leaves it out): on a
|
|
60308
|
+
// field meant to be replaced, the native selection handles are expected.
|
|
60309
|
+
const useSelectOnPress = (props, selectOnPress) => {
|
|
60310
|
+
const engagedRef = useRef(false);
|
|
60311
|
+
const pressEngagesRef = useRef(false);
|
|
60312
|
+
|
|
60313
|
+
if (!selectOnPress) {
|
|
60314
|
+
return null;
|
|
60315
|
+
}
|
|
60316
|
+
const onMouseDown = (e) => {
|
|
60317
|
+
props.onMouseDown(e);
|
|
60318
|
+
if (e.defaultPrevented) {
|
|
60319
|
+
return;
|
|
60320
|
+
}
|
|
60321
|
+
if (e.button !== 0) {
|
|
60322
|
+
return;
|
|
60323
|
+
}
|
|
60324
|
+
pressEngagesRef.current = !engagedRef.current;
|
|
60325
|
+
engagedRef.current = true;
|
|
60326
|
+
};
|
|
60327
|
+
const onClick = (e) => {
|
|
60328
|
+
props.onClick(e);
|
|
60329
|
+
if (!pressEngagesRef.current) {
|
|
60330
|
+
return;
|
|
60331
|
+
}
|
|
60332
|
+
pressEngagesRef.current = false;
|
|
60333
|
+
if (e.defaultPrevented) {
|
|
60334
|
+
return;
|
|
60335
|
+
}
|
|
60336
|
+
const input = e.currentTarget;
|
|
60337
|
+
if (input.selectionStart !== input.selectionEnd) {
|
|
60338
|
+
// The press dragged across a part of the text: that part is what was meant.
|
|
60339
|
+
return;
|
|
60340
|
+
}
|
|
60341
|
+
input.select();
|
|
60342
|
+
// Keep the beginning of the text visible instead of scrolling to the end
|
|
60343
|
+
input.scrollLeft = 0;
|
|
60344
|
+
};
|
|
60345
|
+
const onKeyDown = (e) => {
|
|
60346
|
+
if (!engagedRef.current) {
|
|
60347
|
+
const defaultAction = getKeyboardEventDefaultAction(e);
|
|
60348
|
+
if (defaultAction === "type") {
|
|
60349
|
+
engagedRef.current = true;
|
|
60350
|
+
// Before the control's own keydown: maxLengthGuard measures the room
|
|
60351
|
+
// left against the selection the key is about to replace.
|
|
60352
|
+
e.currentTarget.select();
|
|
60353
|
+
} else if (defaultAction === "cursor_move") {
|
|
60354
|
+
engagedRef.current = true;
|
|
60355
|
+
}
|
|
60356
|
+
}
|
|
60357
|
+
props.onKeyDown(e);
|
|
60358
|
+
};
|
|
60359
|
+
const onBlur = (e) => {
|
|
60360
|
+
props.onBlur(e);
|
|
60361
|
+
if (document.activeElement === e.currentTarget) {
|
|
60362
|
+
// The window went to the background and the field still holds the focus:
|
|
60363
|
+
// coming back is not a new arrival.
|
|
60364
|
+
return;
|
|
60365
|
+
}
|
|
60366
|
+
engagedRef.current = false;
|
|
60367
|
+
};
|
|
60368
|
+
|
|
60369
|
+
return { onMouseDown, onClick, onKeyDown, onBlur };
|
|
60370
|
+
};
|
|
60371
|
+
|
|
60246
60372
|
/**
|
|
60247
60373
|
* Input component for all textual input types.
|
|
60248
60374
|
*
|
|
@@ -60350,7 +60476,8 @@ const InputTextualUI = props => {
|
|
|
60350
60476
|
ui,
|
|
60351
60477
|
variant,
|
|
60352
60478
|
backgroundColor,
|
|
60353
|
-
width = "maxLength"
|
|
60479
|
+
width = "maxLength",
|
|
60480
|
+
selectOnPress
|
|
60354
60481
|
} = props;
|
|
60355
60482
|
const [inputControlRootProps, inputControlHostProps, controlChildrenWrapperProps] = useInputTextualProps(props);
|
|
60356
60483
|
const {
|
|
@@ -60392,6 +60519,8 @@ const InputTextualUI = props => {
|
|
|
60392
60519
|
} else {
|
|
60393
60520
|
inputControlHostProps.width = width;
|
|
60394
60521
|
}
|
|
60522
|
+
delete inputControlRootProps.selectOnPress;
|
|
60523
|
+
inputControlHostProps.selectOnPress = selectOnPress;
|
|
60395
60524
|
return jsxs(Box, {
|
|
60396
60525
|
as: "span",
|
|
60397
60526
|
inline: true,
|
|
@@ -60464,7 +60593,7 @@ const resolveWidthFromMaxLength = (maxLength, inputMode) => {
|
|
|
60464
60593
|
// rest — the guards, the slots, the constraints — which describe an edition
|
|
60465
60594
|
// that does not happen here. What survives is what a Box understands: width,
|
|
60466
60595
|
// spacing, colors, className, style.
|
|
60467
|
-
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"];
|
|
60596
|
+
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"];
|
|
60468
60597
|
const InputTextualAsText = props => {
|
|
60469
60598
|
installInputCss();
|
|
60470
60599
|
// The id a Field handed down, which is what its Label points at.
|
|
@@ -60528,14 +60657,20 @@ const InputTextualFirstResolver = props => {
|
|
|
60528
60657
|
const InputTextual = /*#__PURE__*/createComponentResolver([InputTextualAsTextResolver, InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
|
|
60529
60658
|
const RealInput = ({
|
|
60530
60659
|
maxLength,
|
|
60660
|
+
selectOnPress,
|
|
60531
60661
|
...domProps
|
|
60532
60662
|
}) => {
|
|
60533
60663
|
const autoSelectReadOnlyProps = useAutoSelectReadOnly(domProps);
|
|
60664
|
+
const selectOnPressProps = useSelectOnPress({
|
|
60665
|
+
...domProps,
|
|
60666
|
+
...autoSelectReadOnlyProps
|
|
60667
|
+
}, selectOnPress);
|
|
60534
60668
|
return jsx(Box, {
|
|
60535
60669
|
...domProps,
|
|
60536
60670
|
as: "input",
|
|
60537
60671
|
baseClassName: "navi_control_input",
|
|
60538
60672
|
...autoSelectReadOnlyProps,
|
|
60673
|
+
...selectOnPressProps,
|
|
60539
60674
|
// Never set native maxLength — our guard handles it. Omitting it entirely
|
|
60540
60675
|
// avoids a Preact quirk: setting maxLength={undefined} on a fresh DOM element
|
|
60541
60676
|
// (e.g. after a Suspense remount) causes Preact to run `el.maxLength = ""`
|
|
@@ -76166,6 +76301,11 @@ const ListItemSelectable = props => {
|
|
|
76166
76301
|
...(hasSelectedProp ? {
|
|
76167
76302
|
checked: selected
|
|
76168
76303
|
} : null)
|
|
76304
|
+
},
|
|
76305
|
+
// The host is the visually hidden input carrying the selection: the row
|
|
76306
|
+
// is what the person sees and presses (see onClick above).
|
|
76307
|
+
{
|
|
76308
|
+
listenersOn: "root"
|
|
76169
76309
|
});
|
|
76170
76310
|
const {
|
|
76171
76311
|
checked,
|
|
@@ -77911,6 +78051,19 @@ const useListScrollSync = ({
|
|
|
77911
78051
|
// eslint-disable-next-line no-unused-expressions
|
|
77912
78052
|
virtualItemSizeSignal.value;
|
|
77913
78053
|
}
|
|
78054
|
+
// The hold ending — the user reaching for the list, something else moving
|
|
78055
|
+
// it, the item it opens on never coming — ends its hold on the document with
|
|
78056
|
+
// it (see holdDocumentScroll), rather than at the next render, which nothing
|
|
78057
|
+
// owes: an offset put back on this page meanwhile is the reader's.
|
|
78058
|
+
const releaseDocumentHoldRef = useRef(null);
|
|
78059
|
+
const letGo = () => {
|
|
78060
|
+
startPlaceRef.current.userTookOver = true;
|
|
78061
|
+
const releaseDocumentHold = releaseDocumentHoldRef.current;
|
|
78062
|
+
if (releaseDocumentHold) {
|
|
78063
|
+
releaseDocumentHoldRef.current = null;
|
|
78064
|
+
releaseDocumentHold();
|
|
78065
|
+
}
|
|
78066
|
+
};
|
|
77914
78067
|
// Set around a scroll the list performs itself. What it protects against is
|
|
77915
78068
|
// not the scroll event as such, but what the listener would conclude from it:
|
|
77916
78069
|
// the position it is about to read was chosen to keep the items where they
|
|
@@ -78144,7 +78297,7 @@ const useListScrollSync = ({
|
|
|
78144
78297
|
}
|
|
78145
78298
|
openAt = scrolledFallback;
|
|
78146
78299
|
if (openAt === "start" || openAt === undefined) {
|
|
78147
|
-
|
|
78300
|
+
letGo();
|
|
78148
78301
|
return;
|
|
78149
78302
|
}
|
|
78150
78303
|
} else {
|
|
@@ -78296,13 +78449,39 @@ const useListScrollSync = ({
|
|
|
78296
78449
|
}, []);
|
|
78297
78450
|
// Held on an item of a list scrolling the document: where the document goes is
|
|
78298
78451
|
// this list's to say, measured on the item, not the url's offset in pixels
|
|
78299
|
-
// (see holdDocumentScroll).
|
|
78452
|
+
// (see holdDocumentScroll). The hold read again: a layout effect of this
|
|
78453
|
+
// commit may have let go already (see placeWhereHeld).
|
|
78454
|
+
const routePage = useContext(RoutePageContext);
|
|
78300
78455
|
useLayoutEffect(() => {
|
|
78301
|
-
if (!heldSomewhere || getScroller() !== document.scrollingElement) {
|
|
78456
|
+
if (!heldSomewhere || startPlaceRef.current.userTookOver || getScroller() !== document.scrollingElement) {
|
|
78302
78457
|
return undefined;
|
|
78303
78458
|
}
|
|
78304
|
-
|
|
78305
|
-
|
|
78459
|
+
const releaseDocumentHold = holdDocumentScroll(routePage);
|
|
78460
|
+
releaseDocumentHoldRef.current = releaseDocumentHold;
|
|
78461
|
+
return () => {
|
|
78462
|
+
releaseDocumentHoldRef.current = null;
|
|
78463
|
+
releaseDocumentHold();
|
|
78464
|
+
};
|
|
78465
|
+
}, [heldSomewhere, scrolledWanted, scrollerElResolved]);
|
|
78466
|
+
// A page kept while its route transition plays is hidden until it is taken
|
|
78467
|
+
// down: nothing of the list can be measured there any more, and its scroller
|
|
78468
|
+
// shows the page arriving (see route_page_context.js). So the list lets go of
|
|
78469
|
+
// it, and stops answering its resizes and scrolls: measured hidden, every
|
|
78470
|
+
// item stands at 0×0, and the top item put back where it was would scroll
|
|
78471
|
+
// the page arriving by that item's offset. Kept mounted on purpose, so the
|
|
78472
|
+
// listeners stay; what they would do is what ends.
|
|
78473
|
+
const pageLeftRef = useRef(false);
|
|
78474
|
+
useLayoutEffect(() => {
|
|
78475
|
+
if (!routePage) {
|
|
78476
|
+
return undefined;
|
|
78477
|
+
}
|
|
78478
|
+
return routePage.leavingSignal.subscribe(leaving => {
|
|
78479
|
+
if (leaving) {
|
|
78480
|
+
pageLeftRef.current = true;
|
|
78481
|
+
letGo();
|
|
78482
|
+
}
|
|
78483
|
+
});
|
|
78484
|
+
}, []);
|
|
78306
78485
|
// What to do when the list's own geometry moves under it, kept fresh for the
|
|
78307
78486
|
// observer below (which is installed once).
|
|
78308
78487
|
const onGeometryChangeRef = useRef(null);
|
|
@@ -78336,18 +78515,15 @@ const useListScrollSync = ({
|
|
|
78336
78515
|
return undefined;
|
|
78337
78516
|
}
|
|
78338
78517
|
const scrollerEl = getScroller();
|
|
78339
|
-
const takeOver = () => {
|
|
78340
|
-
startPlaceRef.current.userTookOver = true;
|
|
78341
|
-
};
|
|
78342
78518
|
const takeOverEvents = ["wheel", "touchstart", "pointerdown", "keydown"];
|
|
78343
78519
|
for (const type of takeOverEvents) {
|
|
78344
|
-
scrollerEl.addEventListener(type,
|
|
78520
|
+
scrollerEl.addEventListener(type, letGo, {
|
|
78345
78521
|
passive: true
|
|
78346
78522
|
});
|
|
78347
78523
|
}
|
|
78348
78524
|
return () => {
|
|
78349
78525
|
for (const type of takeOverEvents) {
|
|
78350
|
-
scrollerEl.removeEventListener(type,
|
|
78526
|
+
scrollerEl.removeEventListener(type, letGo);
|
|
78351
78527
|
}
|
|
78352
78528
|
};
|
|
78353
78529
|
}, [scrolledWanted, scrollerElResolved]);
|
|
@@ -78504,6 +78680,9 @@ const useListScrollSync = ({
|
|
|
78504
78680
|
}
|
|
78505
78681
|
};
|
|
78506
78682
|
const observer = new ResizeObserver(entries => {
|
|
78683
|
+
if (pageLeftRef.current) {
|
|
78684
|
+
return;
|
|
78685
|
+
}
|
|
78507
78686
|
// A box that grows may be a box that starts to scroll — the scroller is
|
|
78508
78687
|
// resolved again before anything is done about the resize, so that a
|
|
78509
78688
|
// list which has outgrown a bounded ancestor stops holding on to the
|
|
@@ -78908,6 +79087,9 @@ const useListScrollSync = ({
|
|
|
78908
79087
|
}
|
|
78909
79088
|
const scrollerEl = getScroller();
|
|
78910
79089
|
const onScroll = () => {
|
|
79090
|
+
if (pageLeftRef.current) {
|
|
79091
|
+
return;
|
|
79092
|
+
}
|
|
78911
79093
|
// Which way the user is going, read before the position is stored: what
|
|
78912
79094
|
// the window keeps ahead is ahead of this.
|
|
78913
79095
|
const previousScroll = currentScrollRef.current;
|
|
@@ -78965,7 +79147,7 @@ const useListScrollSync = ({
|
|
|
78965
79147
|
const low = way.from < way.to ? way.from : way.to;
|
|
78966
79148
|
const high = way.from < way.to ? way.to : way.from;
|
|
78967
79149
|
if (position < low - 1 || position > high + 1) {
|
|
78968
|
-
|
|
79150
|
+
letGo();
|
|
78969
79151
|
}
|
|
78970
79152
|
};
|
|
78971
79153
|
// A page-level scroller does not emit "scroll" on the element itself
|
|
@@ -84909,7 +85091,11 @@ const PickerButton = props => {
|
|
|
84909
85091
|
...props,
|
|
84910
85092
|
ref: inputRef
|
|
84911
85093
|
}, {
|
|
84912
|
-
controlType: "picker"
|
|
85094
|
+
controlType: "picker",
|
|
85095
|
+
// The root holds what the picker opens as well as its trigger: a
|
|
85096
|
+
// listener on the picker hears what bubbles out of the popup (the focus
|
|
85097
|
+
// leaving it, see picker_custom.jsx).
|
|
85098
|
+
listenersOn: "root"
|
|
84913
85099
|
});
|
|
84914
85100
|
const uiStateController = getUIStateControllerById(inputProps.id);
|
|
84915
85101
|
const value = uiStateController.uiState;
|
|
@@ -87446,6 +87632,8 @@ const InputDuration = props => {
|
|
|
87446
87632
|
}, {
|
|
87447
87633
|
controlType: "duration_group",
|
|
87448
87634
|
cascadeValidationToChildren: true,
|
|
87635
|
+
// The host is an <input type="hidden"> carrying the value.
|
|
87636
|
+
listenersOn: "root",
|
|
87449
87637
|
// Aggregates sub-input values into an ISO 8601-like duration string.
|
|
87450
87638
|
// durationToISOString preserves non-numeric mid-edit values
|
|
87451
87639
|
// (e.g. hours="ab") between their unit markers ("PTabH30M"),
|
|
@@ -87512,7 +87700,7 @@ const InputDuration = props => {
|
|
|
87512
87700
|
} = groupHostProps;
|
|
87513
87701
|
const groupRef = useRef();
|
|
87514
87702
|
useInputGroup(groupRef);
|
|
87515
|
-
const clipboardProps = useClipboardProps(groupRef);
|
|
87703
|
+
const clipboardProps = useClipboardProps(groupRef, groupRootProps);
|
|
87516
87704
|
const hourValue = components?.hours;
|
|
87517
87705
|
const minuteValue = components?.minutes;
|
|
87518
87706
|
const rawSecondValue = components?.seconds;
|
|
@@ -87585,7 +87773,11 @@ const InputDuration = props => {
|
|
|
87585
87773
|
const getVisibleFields = container => {
|
|
87586
87774
|
return ["hour", "minute", "second", "millisecond"].filter(f => container.querySelector(`[navi-input-type="${f}"]`) !== null);
|
|
87587
87775
|
};
|
|
87588
|
-
|
|
87776
|
+
|
|
87777
|
+
// Spread after the group's props, so the caller's listeners for these events
|
|
87778
|
+
// are called from here. Whatever they do, the group's own reading runs too, as
|
|
87779
|
+
// it does after a field's paste guard prevented the paste.
|
|
87780
|
+
const useClipboardProps = (groupRef, callerProps) => {
|
|
87589
87781
|
const getClipboardPayload = () => {
|
|
87590
87782
|
const groupEl = groupRef.current;
|
|
87591
87783
|
if (!groupEl) {
|
|
@@ -87651,6 +87843,7 @@ const useClipboardProps = groupRef => {
|
|
|
87651
87843
|
return true;
|
|
87652
87844
|
};
|
|
87653
87845
|
const onCopy = e => {
|
|
87846
|
+
callerProps.onCopy?.(e);
|
|
87654
87847
|
if (!isFromDurationField(e)) {
|
|
87655
87848
|
return;
|
|
87656
87849
|
}
|
|
@@ -87663,6 +87856,7 @@ const useClipboardProps = groupRef => {
|
|
|
87663
87856
|
e.preventDefault();
|
|
87664
87857
|
};
|
|
87665
87858
|
const onCut = e => {
|
|
87859
|
+
callerProps.onCut?.(e);
|
|
87666
87860
|
if (!isFromDurationField(e)) {
|
|
87667
87861
|
return;
|
|
87668
87862
|
}
|
|
@@ -87675,6 +87869,7 @@ const useClipboardProps = groupRef => {
|
|
|
87675
87869
|
applyToGroup("", e);
|
|
87676
87870
|
};
|
|
87677
87871
|
const onPaste = e => {
|
|
87872
|
+
callerProps.onPaste?.(e);
|
|
87678
87873
|
if (!isFromDurationField(e)) {
|
|
87679
87874
|
return;
|
|
87680
87875
|
}
|
|
@@ -90240,7 +90435,10 @@ function WheelUI(props) {
|
|
|
90240
90435
|
// action explicitly on settle. A caller can still override actionEvent.
|
|
90241
90436
|
actionEvent: props.actionEvent ?? "custom"
|
|
90242
90437
|
}, {
|
|
90243
|
-
controlType: "input"
|
|
90438
|
+
controlType: "input",
|
|
90439
|
+
// The host is the hidden input carrying the value: the container is
|
|
90440
|
+
// what is focused and spun.
|
|
90441
|
+
listenersOn: "root"
|
|
90244
90442
|
});
|
|
90245
90443
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
90246
90444
|
const {
|