@jsenv/navi 0.29.476 → 0.29.477
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 +161 -18
- package/dist/dev/jsenv_navi.js.map +11 -9
- package/dist/jsenv_navi.js +161 -18
- package/dist/jsenv_navi.js.map +11 -9
- 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",
|
|
@@ -45739,6 +45734,9 @@ const ControlgroupChildrenWrapper = ({
|
|
|
45739
45734
|
})
|
|
45740
45735
|
});
|
|
45741
45736
|
|
|
45737
|
+
// What the caller writes for these is called from the control's own reactions
|
|
45738
|
+
// (see `reactions` below), so the split leaves them out of both sides.
|
|
45739
|
+
const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyDown", "onPaste", "onInput"]);
|
|
45742
45740
|
/**
|
|
45743
45741
|
* Core hook for interactive field components (InputTextual, InputCheckbox, etc.).
|
|
45744
45742
|
*
|
|
@@ -45753,12 +45751,22 @@ const ControlgroupChildrenWrapper = ({
|
|
|
45753
45751
|
* All state changes route through DOM events on the field element so that
|
|
45754
45752
|
* external subscribers (e.g. useControlUIState, Selectable) receive every update.
|
|
45755
45753
|
*
|
|
45754
|
+
* @param {object} options
|
|
45755
|
+
* @param {"host"|"root"} [options.listenersOn] Where the caller's DOM listeners
|
|
45756
|
+
* (`onPointerDown`, `onFocus`…, never navi's own `onnavi_*`) land. `"host"`,
|
|
45757
|
+
* the default, is right wherever the host is what the person presses and
|
|
45758
|
+
* focuses: the field inside its box, or one element for both. `"root"` is for
|
|
45759
|
+
* a control whose host is a hidden carrier, or whose root also holds what
|
|
45760
|
+
* the control opens, so that a listener hears the control as a whole.
|
|
45761
|
+
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
45762
|
+
* host's either way: the control's own reactions to them call them.
|
|
45756
45763
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
45757
45764
|
*/
|
|
45758
45765
|
const useControlProps = (props, {
|
|
45759
45766
|
controlType,
|
|
45760
45767
|
persists,
|
|
45761
|
-
uiActionInternal
|
|
45768
|
+
uiActionInternal,
|
|
45769
|
+
listenersOn = "host"
|
|
45762
45770
|
}) => {
|
|
45763
45771
|
const debugUIState = useDebugUIState();
|
|
45764
45772
|
|
|
@@ -45844,7 +45852,9 @@ const useControlProps = (props, {
|
|
|
45844
45852
|
const [controlRootProps, controlHostProps] = useInteractiveProps(props, {
|
|
45845
45853
|
uiStateController,
|
|
45846
45854
|
boundAction,
|
|
45847
|
-
controlInfo
|
|
45855
|
+
controlInfo,
|
|
45856
|
+
listenersOn,
|
|
45857
|
+
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
45848
45858
|
});
|
|
45849
45859
|
{
|
|
45850
45860
|
const debugInteraction = useDebugInteraction();
|
|
@@ -46878,7 +46888,9 @@ const useControlgroupProps = (props, {
|
|
|
46878
46888
|
wantRequesterButtonState,
|
|
46879
46889
|
uiActionInternal,
|
|
46880
46890
|
allowCapture = false,
|
|
46881
|
-
cascadeValidationToChildren = false
|
|
46891
|
+
cascadeValidationToChildren = false,
|
|
46892
|
+
// See useControlProps.
|
|
46893
|
+
listenersOn = "host"
|
|
46882
46894
|
}) => {
|
|
46883
46895
|
const {
|
|
46884
46896
|
action,
|
|
@@ -46928,7 +46940,8 @@ const useControlgroupProps = (props, {
|
|
|
46928
46940
|
// so we don't have a value concept, nor readonly etc
|
|
46929
46941
|
controlInfo: {
|
|
46930
46942
|
controlType
|
|
46931
|
-
}
|
|
46943
|
+
},
|
|
46944
|
+
listenersOn
|
|
46932
46945
|
});
|
|
46933
46946
|
const {
|
|
46934
46947
|
basePseudoState
|
|
@@ -47067,12 +47080,17 @@ const ControlFacadeChildrenWrapper = ({
|
|
|
47067
47080
|
const useInteractiveProps = (props, {
|
|
47068
47081
|
uiStateController,
|
|
47069
47082
|
boundAction,
|
|
47070
|
-
controlInfo
|
|
47083
|
+
controlInfo,
|
|
47084
|
+
listenersOn,
|
|
47085
|
+
listenersCalledByReactions
|
|
47071
47086
|
}) => {
|
|
47072
47087
|
const {
|
|
47073
47088
|
ref
|
|
47074
47089
|
} = props;
|
|
47075
|
-
const [controlRootProps, controlHostProps] = splitControlProps(props
|
|
47090
|
+
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
47091
|
+
listenersOn,
|
|
47092
|
+
listenersCalledByReactions
|
|
47093
|
+
});
|
|
47076
47094
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
47077
47095
|
if (props.selfInteractions) {
|
|
47078
47096
|
// One attribute, in the DOM, because that is where the claim is read from —
|
|
@@ -47449,8 +47467,10 @@ const useInteractiveProps = (props, {
|
|
|
47449
47467
|
}
|
|
47450
47468
|
}
|
|
47451
47469
|
};
|
|
47470
|
+
const onFocusFromProps = controlHostProps.onFocus;
|
|
47452
47471
|
Object.assign(controlHostProps, {
|
|
47453
47472
|
onFocus: e => {
|
|
47473
|
+
onFocusFromProps?.(e);
|
|
47454
47474
|
// Transfer programmatic focus to the delegate target (navi-focus-delegate or navi-control-proxy-for)
|
|
47455
47475
|
const focusProxyTarget = findFocusDelegateTarget(e.currentTarget) || findControlProxyTarget(e.currentTarget);
|
|
47456
47476
|
if (focusProxyTarget) {
|
|
@@ -47680,7 +47700,10 @@ const useInteractiveProps = (props, {
|
|
|
47680
47700
|
}
|
|
47681
47701
|
return [controlRootProps, controlHostProps];
|
|
47682
47702
|
};
|
|
47683
|
-
const splitControlProps = props
|
|
47703
|
+
const splitControlProps = (props, {
|
|
47704
|
+
listenersOn,
|
|
47705
|
+
listenersCalledByReactions
|
|
47706
|
+
}) => {
|
|
47684
47707
|
const {
|
|
47685
47708
|
ref
|
|
47686
47709
|
} = props;
|
|
@@ -47697,10 +47720,20 @@ const splitControlProps = props => {
|
|
|
47697
47720
|
if (isControlProp(key)) {
|
|
47698
47721
|
continue;
|
|
47699
47722
|
}
|
|
47723
|
+
if (listenersCalledByReactions?.has(key)) {
|
|
47724
|
+
continue;
|
|
47725
|
+
}
|
|
47726
|
+
if (listenersOn === "host" && isDomListenerProp(key)) {
|
|
47727
|
+
controlHostProps[key] = props[key];
|
|
47728
|
+
continue;
|
|
47729
|
+
}
|
|
47700
47730
|
controlRootProps[key] = props[key];
|
|
47701
47731
|
}
|
|
47702
47732
|
return [controlRootProps, controlHostProps];
|
|
47703
47733
|
};
|
|
47734
|
+
// `onPointerDown`, `onFocusOut`… but not navi's own events (`onnavi_command`),
|
|
47735
|
+
// which keep the element they are dispatched on.
|
|
47736
|
+
const isDomListenerProp = key => /^on[A-Z]/.test(key);
|
|
47704
47737
|
const isCheckableInput = (controlType, typeProp) => controlType === "input" && (typeProp === "radio" || typeProp === "checkbox");
|
|
47705
47738
|
|
|
47706
47739
|
// The labels the DOM itself can hand over: a wrapping <label>, or a label[for]
|
|
@@ -60243,6 +60276,86 @@ const useAutoSelectReadOnly = (props) => {
|
|
|
60243
60276
|
return { onFocus, onMouseDown, onPointerDown };
|
|
60244
60277
|
};
|
|
60245
60278
|
|
|
60279
|
+
// selectOnPress: a field whose value is usually replaced rather than edited (an
|
|
60280
|
+
// address, a place to search) behaves like the address bar of a browser. The
|
|
60281
|
+
// first gesture that engages it after it took the focus selects its whole text,
|
|
60282
|
+
// so typing replaces it; a second press places the caret, to fix one part.
|
|
60283
|
+
//
|
|
60284
|
+
// Engaged by a gesture, never by the focus itself: a dialog opening hands the
|
|
60285
|
+
// focus to its first field (docs/autofocus.md), and a field selected on focus
|
|
60286
|
+
// would greet whoever opened the dialog already half-way into an edit. So a
|
|
60287
|
+
// focus that came without a press waits for the first key that types.
|
|
60288
|
+
//
|
|
60289
|
+
// The press selects once it is over, on `click`: the browser places the caret
|
|
60290
|
+
// on mousedown and may collapse a selection on mouseup, so anything selected
|
|
60291
|
+
// before the release is undone by it. `mousedown` decides whether a press
|
|
60292
|
+
// engages because a finger only produces one after a tap — a finger that
|
|
60293
|
+
// scrolled the page never reaches the field. Touch is included on purpose (see
|
|
60294
|
+
// use_autoselect_read_only.js for why a read-only field leaves it out): on a
|
|
60295
|
+
// field meant to be replaced, the native selection handles are expected.
|
|
60296
|
+
const useSelectOnPress = (props, selectOnPress) => {
|
|
60297
|
+
const engagedRef = useRef(false);
|
|
60298
|
+
const pressEngagesRef = useRef(false);
|
|
60299
|
+
|
|
60300
|
+
if (!selectOnPress) {
|
|
60301
|
+
return null;
|
|
60302
|
+
}
|
|
60303
|
+
const onMouseDown = (e) => {
|
|
60304
|
+
props.onMouseDown(e);
|
|
60305
|
+
if (e.defaultPrevented) {
|
|
60306
|
+
return;
|
|
60307
|
+
}
|
|
60308
|
+
if (e.button !== 0) {
|
|
60309
|
+
return;
|
|
60310
|
+
}
|
|
60311
|
+
pressEngagesRef.current = !engagedRef.current;
|
|
60312
|
+
engagedRef.current = true;
|
|
60313
|
+
};
|
|
60314
|
+
const onClick = (e) => {
|
|
60315
|
+
props.onClick(e);
|
|
60316
|
+
if (!pressEngagesRef.current) {
|
|
60317
|
+
return;
|
|
60318
|
+
}
|
|
60319
|
+
pressEngagesRef.current = false;
|
|
60320
|
+
if (e.defaultPrevented) {
|
|
60321
|
+
return;
|
|
60322
|
+
}
|
|
60323
|
+
const input = e.currentTarget;
|
|
60324
|
+
if (input.selectionStart !== input.selectionEnd) {
|
|
60325
|
+
// The press dragged across a part of the text: that part is what was meant.
|
|
60326
|
+
return;
|
|
60327
|
+
}
|
|
60328
|
+
input.select();
|
|
60329
|
+
// Keep the beginning of the text visible instead of scrolling to the end
|
|
60330
|
+
input.scrollLeft = 0;
|
|
60331
|
+
};
|
|
60332
|
+
const onKeyDown = (e) => {
|
|
60333
|
+
if (!engagedRef.current) {
|
|
60334
|
+
const defaultAction = getKeyboardEventDefaultAction(e);
|
|
60335
|
+
if (defaultAction === "type") {
|
|
60336
|
+
engagedRef.current = true;
|
|
60337
|
+
// Before the control's own keydown: maxLengthGuard measures the room
|
|
60338
|
+
// left against the selection the key is about to replace.
|
|
60339
|
+
e.currentTarget.select();
|
|
60340
|
+
} else if (defaultAction === "cursor_move") {
|
|
60341
|
+
engagedRef.current = true;
|
|
60342
|
+
}
|
|
60343
|
+
}
|
|
60344
|
+
props.onKeyDown(e);
|
|
60345
|
+
};
|
|
60346
|
+
const onBlur = (e) => {
|
|
60347
|
+
props.onBlur(e);
|
|
60348
|
+
if (document.activeElement === e.currentTarget) {
|
|
60349
|
+
// The window went to the background and the field still holds the focus:
|
|
60350
|
+
// coming back is not a new arrival.
|
|
60351
|
+
return;
|
|
60352
|
+
}
|
|
60353
|
+
engagedRef.current = false;
|
|
60354
|
+
};
|
|
60355
|
+
|
|
60356
|
+
return { onMouseDown, onClick, onKeyDown, onBlur };
|
|
60357
|
+
};
|
|
60358
|
+
|
|
60246
60359
|
/**
|
|
60247
60360
|
* Input component for all textual input types.
|
|
60248
60361
|
*
|
|
@@ -60350,7 +60463,8 @@ const InputTextualUI = props => {
|
|
|
60350
60463
|
ui,
|
|
60351
60464
|
variant,
|
|
60352
60465
|
backgroundColor,
|
|
60353
|
-
width = "maxLength"
|
|
60466
|
+
width = "maxLength",
|
|
60467
|
+
selectOnPress
|
|
60354
60468
|
} = props;
|
|
60355
60469
|
const [inputControlRootProps, inputControlHostProps, controlChildrenWrapperProps] = useInputTextualProps(props);
|
|
60356
60470
|
const {
|
|
@@ -60392,6 +60506,8 @@ const InputTextualUI = props => {
|
|
|
60392
60506
|
} else {
|
|
60393
60507
|
inputControlHostProps.width = width;
|
|
60394
60508
|
}
|
|
60509
|
+
delete inputControlRootProps.selectOnPress;
|
|
60510
|
+
inputControlHostProps.selectOnPress = selectOnPress;
|
|
60395
60511
|
return jsxs(Box, {
|
|
60396
60512
|
as: "span",
|
|
60397
60513
|
inline: true,
|
|
@@ -60464,7 +60580,7 @@ const resolveWidthFromMaxLength = (maxLength, inputMode) => {
|
|
|
60464
60580
|
// rest — the guards, the slots, the constraints — which describe an edition
|
|
60465
60581
|
// that does not happen here. What survives is what a Box understands: width,
|
|
60466
60582
|
// 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"];
|
|
60583
|
+
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
60584
|
const InputTextualAsText = props => {
|
|
60469
60585
|
installInputCss();
|
|
60470
60586
|
// The id a Field handed down, which is what its Label points at.
|
|
@@ -60528,14 +60644,20 @@ const InputTextualFirstResolver = props => {
|
|
|
60528
60644
|
const InputTextual = /*#__PURE__*/createComponentResolver([InputTextualAsTextResolver, InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
|
|
60529
60645
|
const RealInput = ({
|
|
60530
60646
|
maxLength,
|
|
60647
|
+
selectOnPress,
|
|
60531
60648
|
...domProps
|
|
60532
60649
|
}) => {
|
|
60533
60650
|
const autoSelectReadOnlyProps = useAutoSelectReadOnly(domProps);
|
|
60651
|
+
const selectOnPressProps = useSelectOnPress({
|
|
60652
|
+
...domProps,
|
|
60653
|
+
...autoSelectReadOnlyProps
|
|
60654
|
+
}, selectOnPress);
|
|
60534
60655
|
return jsx(Box, {
|
|
60535
60656
|
...domProps,
|
|
60536
60657
|
as: "input",
|
|
60537
60658
|
baseClassName: "navi_control_input",
|
|
60538
60659
|
...autoSelectReadOnlyProps,
|
|
60660
|
+
...selectOnPressProps,
|
|
60539
60661
|
// Never set native maxLength — our guard handles it. Omitting it entirely
|
|
60540
60662
|
// avoids a Preact quirk: setting maxLength={undefined} on a fresh DOM element
|
|
60541
60663
|
// (e.g. after a Suspense remount) causes Preact to run `el.maxLength = ""`
|
|
@@ -76166,6 +76288,11 @@ const ListItemSelectable = props => {
|
|
|
76166
76288
|
...(hasSelectedProp ? {
|
|
76167
76289
|
checked: selected
|
|
76168
76290
|
} : null)
|
|
76291
|
+
},
|
|
76292
|
+
// The host is the visually hidden input carrying the selection: the row
|
|
76293
|
+
// is what the person sees and presses (see onClick above).
|
|
76294
|
+
{
|
|
76295
|
+
listenersOn: "root"
|
|
76169
76296
|
});
|
|
76170
76297
|
const {
|
|
76171
76298
|
checked,
|
|
@@ -84909,7 +85036,11 @@ const PickerButton = props => {
|
|
|
84909
85036
|
...props,
|
|
84910
85037
|
ref: inputRef
|
|
84911
85038
|
}, {
|
|
84912
|
-
controlType: "picker"
|
|
85039
|
+
controlType: "picker",
|
|
85040
|
+
// The root holds what the picker opens as well as its trigger: a
|
|
85041
|
+
// listener on the picker hears what bubbles out of the popup (the focus
|
|
85042
|
+
// leaving it, see picker_custom.jsx).
|
|
85043
|
+
listenersOn: "root"
|
|
84913
85044
|
});
|
|
84914
85045
|
const uiStateController = getUIStateControllerById(inputProps.id);
|
|
84915
85046
|
const value = uiStateController.uiState;
|
|
@@ -87446,6 +87577,8 @@ const InputDuration = props => {
|
|
|
87446
87577
|
}, {
|
|
87447
87578
|
controlType: "duration_group",
|
|
87448
87579
|
cascadeValidationToChildren: true,
|
|
87580
|
+
// The host is an <input type="hidden"> carrying the value.
|
|
87581
|
+
listenersOn: "root",
|
|
87449
87582
|
// Aggregates sub-input values into an ISO 8601-like duration string.
|
|
87450
87583
|
// durationToISOString preserves non-numeric mid-edit values
|
|
87451
87584
|
// (e.g. hours="ab") between their unit markers ("PTabH30M"),
|
|
@@ -87512,7 +87645,7 @@ const InputDuration = props => {
|
|
|
87512
87645
|
} = groupHostProps;
|
|
87513
87646
|
const groupRef = useRef();
|
|
87514
87647
|
useInputGroup(groupRef);
|
|
87515
|
-
const clipboardProps = useClipboardProps(groupRef);
|
|
87648
|
+
const clipboardProps = useClipboardProps(groupRef, groupRootProps);
|
|
87516
87649
|
const hourValue = components?.hours;
|
|
87517
87650
|
const minuteValue = components?.minutes;
|
|
87518
87651
|
const rawSecondValue = components?.seconds;
|
|
@@ -87585,7 +87718,11 @@ const InputDuration = props => {
|
|
|
87585
87718
|
const getVisibleFields = container => {
|
|
87586
87719
|
return ["hour", "minute", "second", "millisecond"].filter(f => container.querySelector(`[navi-input-type="${f}"]`) !== null);
|
|
87587
87720
|
};
|
|
87588
|
-
|
|
87721
|
+
|
|
87722
|
+
// Spread after the group's props, so the caller's listeners for these events
|
|
87723
|
+
// are called from here. Whatever they do, the group's own reading runs too, as
|
|
87724
|
+
// it does after a field's paste guard prevented the paste.
|
|
87725
|
+
const useClipboardProps = (groupRef, callerProps) => {
|
|
87589
87726
|
const getClipboardPayload = () => {
|
|
87590
87727
|
const groupEl = groupRef.current;
|
|
87591
87728
|
if (!groupEl) {
|
|
@@ -87651,6 +87788,7 @@ const useClipboardProps = groupRef => {
|
|
|
87651
87788
|
return true;
|
|
87652
87789
|
};
|
|
87653
87790
|
const onCopy = e => {
|
|
87791
|
+
callerProps.onCopy?.(e);
|
|
87654
87792
|
if (!isFromDurationField(e)) {
|
|
87655
87793
|
return;
|
|
87656
87794
|
}
|
|
@@ -87663,6 +87801,7 @@ const useClipboardProps = groupRef => {
|
|
|
87663
87801
|
e.preventDefault();
|
|
87664
87802
|
};
|
|
87665
87803
|
const onCut = e => {
|
|
87804
|
+
callerProps.onCut?.(e);
|
|
87666
87805
|
if (!isFromDurationField(e)) {
|
|
87667
87806
|
return;
|
|
87668
87807
|
}
|
|
@@ -87675,6 +87814,7 @@ const useClipboardProps = groupRef => {
|
|
|
87675
87814
|
applyToGroup("", e);
|
|
87676
87815
|
};
|
|
87677
87816
|
const onPaste = e => {
|
|
87817
|
+
callerProps.onPaste?.(e);
|
|
87678
87818
|
if (!isFromDurationField(e)) {
|
|
87679
87819
|
return;
|
|
87680
87820
|
}
|
|
@@ -90240,7 +90380,10 @@ function WheelUI(props) {
|
|
|
90240
90380
|
// action explicitly on settle. A caller can still override actionEvent.
|
|
90241
90381
|
actionEvent: props.actionEvent ?? "custom"
|
|
90242
90382
|
}, {
|
|
90243
|
-
controlType: "input"
|
|
90383
|
+
controlType: "input",
|
|
90384
|
+
// The host is the hidden input carrying the value: the container is
|
|
90385
|
+
// what is focused and spun.
|
|
90386
|
+
listenersOn: "root"
|
|
90244
90387
|
});
|
|
90245
90388
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
90246
90389
|
const {
|