@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/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",
|
|
@@ -46903,6 +46898,9 @@ const ControlgroupChildrenWrapper = ({
|
|
|
46903
46898
|
})
|
|
46904
46899
|
});
|
|
46905
46900
|
|
|
46901
|
+
// What the caller writes for these is called from the control's own reactions
|
|
46902
|
+
// (see `reactions` below), so the split leaves them out of both sides.
|
|
46903
|
+
const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyDown", "onPaste", "onInput"]);
|
|
46906
46904
|
/**
|
|
46907
46905
|
* Core hook for interactive field components (InputTextual, InputCheckbox, etc.).
|
|
46908
46906
|
*
|
|
@@ -46917,12 +46915,22 @@ const ControlgroupChildrenWrapper = ({
|
|
|
46917
46915
|
* All state changes route through DOM events on the field element so that
|
|
46918
46916
|
* external subscribers (e.g. useControlUIState, Selectable) receive every update.
|
|
46919
46917
|
*
|
|
46918
|
+
* @param {object} options
|
|
46919
|
+
* @param {"host"|"root"} [options.listenersOn] Where the caller's DOM listeners
|
|
46920
|
+
* (`onPointerDown`, `onFocus`…, never navi's own `onnavi_*`) land. `"host"`,
|
|
46921
|
+
* the default, is right wherever the host is what the person presses and
|
|
46922
|
+
* focuses: the field inside its box, or one element for both. `"root"` is for
|
|
46923
|
+
* a control whose host is a hidden carrier, or whose root also holds what
|
|
46924
|
+
* the control opens, so that a listener hears the control as a whole.
|
|
46925
|
+
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
46926
|
+
* host's either way: the control's own reactions to them call them.
|
|
46920
46927
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
46921
46928
|
*/
|
|
46922
46929
|
const useControlProps = (props, {
|
|
46923
46930
|
controlType,
|
|
46924
46931
|
persists,
|
|
46925
|
-
uiActionInternal
|
|
46932
|
+
uiActionInternal,
|
|
46933
|
+
listenersOn = "host"
|
|
46926
46934
|
}) => {
|
|
46927
46935
|
const debugUIState = useDebugUIState();
|
|
46928
46936
|
const debugAction = useDebugAction();
|
|
@@ -47009,7 +47017,9 @@ const useControlProps = (props, {
|
|
|
47009
47017
|
const [controlRootProps, controlHostProps] = useInteractiveProps(props, {
|
|
47010
47018
|
uiStateController,
|
|
47011
47019
|
boundAction,
|
|
47012
|
-
controlInfo
|
|
47020
|
+
controlInfo,
|
|
47021
|
+
listenersOn,
|
|
47022
|
+
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
47013
47023
|
});
|
|
47014
47024
|
{
|
|
47015
47025
|
const debugInteraction = useDebugInteraction();
|
|
@@ -48062,7 +48072,9 @@ const useControlgroupProps = (props, {
|
|
|
48062
48072
|
wantRequesterButtonState,
|
|
48063
48073
|
uiActionInternal,
|
|
48064
48074
|
allowCapture = false,
|
|
48065
|
-
cascadeValidationToChildren = false
|
|
48075
|
+
cascadeValidationToChildren = false,
|
|
48076
|
+
// See useControlProps.
|
|
48077
|
+
listenersOn = "host"
|
|
48066
48078
|
}) => {
|
|
48067
48079
|
const {
|
|
48068
48080
|
action,
|
|
@@ -48116,7 +48128,8 @@ const useControlgroupProps = (props, {
|
|
|
48116
48128
|
// so we don't have a value concept, nor readonly etc
|
|
48117
48129
|
controlInfo: {
|
|
48118
48130
|
controlType
|
|
48119
|
-
}
|
|
48131
|
+
},
|
|
48132
|
+
listenersOn
|
|
48120
48133
|
});
|
|
48121
48134
|
const {
|
|
48122
48135
|
basePseudoState
|
|
@@ -48255,12 +48268,17 @@ const ControlFacadeChildrenWrapper = ({
|
|
|
48255
48268
|
const useInteractiveProps = (props, {
|
|
48256
48269
|
uiStateController,
|
|
48257
48270
|
boundAction,
|
|
48258
|
-
controlInfo
|
|
48271
|
+
controlInfo,
|
|
48272
|
+
listenersOn,
|
|
48273
|
+
listenersCalledByReactions
|
|
48259
48274
|
}) => {
|
|
48260
48275
|
const {
|
|
48261
48276
|
ref
|
|
48262
48277
|
} = props;
|
|
48263
|
-
const [controlRootProps, controlHostProps] = splitControlProps(props
|
|
48278
|
+
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
48279
|
+
listenersOn,
|
|
48280
|
+
listenersCalledByReactions
|
|
48281
|
+
});
|
|
48264
48282
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
48265
48283
|
if (props.selfInteractions) {
|
|
48266
48284
|
// One attribute, in the DOM, because that is where the claim is read from —
|
|
@@ -48637,8 +48655,10 @@ const useInteractiveProps = (props, {
|
|
|
48637
48655
|
}
|
|
48638
48656
|
}
|
|
48639
48657
|
};
|
|
48658
|
+
const onFocusFromProps = controlHostProps.onFocus;
|
|
48640
48659
|
Object.assign(controlHostProps, {
|
|
48641
48660
|
onFocus: e => {
|
|
48661
|
+
onFocusFromProps?.(e);
|
|
48642
48662
|
// Transfer programmatic focus to the delegate target (navi-focus-delegate or navi-control-proxy-for)
|
|
48643
48663
|
const focusProxyTarget = findFocusDelegateTarget(e.currentTarget) || findControlProxyTarget(e.currentTarget);
|
|
48644
48664
|
if (focusProxyTarget) {
|
|
@@ -48870,7 +48890,10 @@ const useInteractiveProps = (props, {
|
|
|
48870
48890
|
}
|
|
48871
48891
|
return [controlRootProps, controlHostProps];
|
|
48872
48892
|
};
|
|
48873
|
-
const splitControlProps = props
|
|
48893
|
+
const splitControlProps = (props, {
|
|
48894
|
+
listenersOn,
|
|
48895
|
+
listenersCalledByReactions
|
|
48896
|
+
}) => {
|
|
48874
48897
|
const {
|
|
48875
48898
|
ref
|
|
48876
48899
|
} = props;
|
|
@@ -48887,10 +48910,20 @@ const splitControlProps = props => {
|
|
|
48887
48910
|
if (isControlProp(key)) {
|
|
48888
48911
|
continue;
|
|
48889
48912
|
}
|
|
48913
|
+
if (listenersCalledByReactions?.has(key)) {
|
|
48914
|
+
continue;
|
|
48915
|
+
}
|
|
48916
|
+
if (listenersOn === "host" && isDomListenerProp(key)) {
|
|
48917
|
+
controlHostProps[key] = props[key];
|
|
48918
|
+
continue;
|
|
48919
|
+
}
|
|
48890
48920
|
controlRootProps[key] = props[key];
|
|
48891
48921
|
}
|
|
48892
48922
|
return [controlRootProps, controlHostProps];
|
|
48893
48923
|
};
|
|
48924
|
+
// `onPointerDown`, `onFocusOut`… but not navi's own events (`onnavi_command`),
|
|
48925
|
+
// which keep the element they are dispatched on.
|
|
48926
|
+
const isDomListenerProp = key => /^on[A-Z]/.test(key);
|
|
48894
48927
|
const isCheckableInput = (controlType, typeProp) => controlType === "input" && (typeProp === "radio" || typeProp === "checkbox");
|
|
48895
48928
|
|
|
48896
48929
|
// The labels the DOM itself can hand over: a wrapping <label>, or a label[for]
|
|
@@ -61469,6 +61502,86 @@ const useAutoSelectReadOnly = (props) => {
|
|
|
61469
61502
|
return { onFocus, onMouseDown, onPointerDown };
|
|
61470
61503
|
};
|
|
61471
61504
|
|
|
61505
|
+
// selectOnPress: a field whose value is usually replaced rather than edited (an
|
|
61506
|
+
// address, a place to search) behaves like the address bar of a browser. The
|
|
61507
|
+
// first gesture that engages it after it took the focus selects its whole text,
|
|
61508
|
+
// so typing replaces it; a second press places the caret, to fix one part.
|
|
61509
|
+
//
|
|
61510
|
+
// Engaged by a gesture, never by the focus itself: a dialog opening hands the
|
|
61511
|
+
// focus to its first field (docs/autofocus.md), and a field selected on focus
|
|
61512
|
+
// would greet whoever opened the dialog already half-way into an edit. So a
|
|
61513
|
+
// focus that came without a press waits for the first key that types.
|
|
61514
|
+
//
|
|
61515
|
+
// The press selects once it is over, on `click`: the browser places the caret
|
|
61516
|
+
// on mousedown and may collapse a selection on mouseup, so anything selected
|
|
61517
|
+
// before the release is undone by it. `mousedown` decides whether a press
|
|
61518
|
+
// engages because a finger only produces one after a tap — a finger that
|
|
61519
|
+
// scrolled the page never reaches the field. Touch is included on purpose (see
|
|
61520
|
+
// use_autoselect_read_only.js for why a read-only field leaves it out): on a
|
|
61521
|
+
// field meant to be replaced, the native selection handles are expected.
|
|
61522
|
+
const useSelectOnPress = (props, selectOnPress) => {
|
|
61523
|
+
const engagedRef = useRef(false);
|
|
61524
|
+
const pressEngagesRef = useRef(false);
|
|
61525
|
+
|
|
61526
|
+
if (!selectOnPress) {
|
|
61527
|
+
return null;
|
|
61528
|
+
}
|
|
61529
|
+
const onMouseDown = (e) => {
|
|
61530
|
+
props.onMouseDown(e);
|
|
61531
|
+
if (e.defaultPrevented) {
|
|
61532
|
+
return;
|
|
61533
|
+
}
|
|
61534
|
+
if (e.button !== 0) {
|
|
61535
|
+
return;
|
|
61536
|
+
}
|
|
61537
|
+
pressEngagesRef.current = !engagedRef.current;
|
|
61538
|
+
engagedRef.current = true;
|
|
61539
|
+
};
|
|
61540
|
+
const onClick = (e) => {
|
|
61541
|
+
props.onClick(e);
|
|
61542
|
+
if (!pressEngagesRef.current) {
|
|
61543
|
+
return;
|
|
61544
|
+
}
|
|
61545
|
+
pressEngagesRef.current = false;
|
|
61546
|
+
if (e.defaultPrevented) {
|
|
61547
|
+
return;
|
|
61548
|
+
}
|
|
61549
|
+
const input = e.currentTarget;
|
|
61550
|
+
if (input.selectionStart !== input.selectionEnd) {
|
|
61551
|
+
// The press dragged across a part of the text: that part is what was meant.
|
|
61552
|
+
return;
|
|
61553
|
+
}
|
|
61554
|
+
input.select();
|
|
61555
|
+
// Keep the beginning of the text visible instead of scrolling to the end
|
|
61556
|
+
input.scrollLeft = 0;
|
|
61557
|
+
};
|
|
61558
|
+
const onKeyDown = (e) => {
|
|
61559
|
+
if (!engagedRef.current) {
|
|
61560
|
+
const defaultAction = getKeyboardEventDefaultAction(e);
|
|
61561
|
+
if (defaultAction === "type") {
|
|
61562
|
+
engagedRef.current = true;
|
|
61563
|
+
// Before the control's own keydown: maxLengthGuard measures the room
|
|
61564
|
+
// left against the selection the key is about to replace.
|
|
61565
|
+
e.currentTarget.select();
|
|
61566
|
+
} else if (defaultAction === "cursor_move") {
|
|
61567
|
+
engagedRef.current = true;
|
|
61568
|
+
}
|
|
61569
|
+
}
|
|
61570
|
+
props.onKeyDown(e);
|
|
61571
|
+
};
|
|
61572
|
+
const onBlur = (e) => {
|
|
61573
|
+
props.onBlur(e);
|
|
61574
|
+
if (document.activeElement === e.currentTarget) {
|
|
61575
|
+
// The window went to the background and the field still holds the focus:
|
|
61576
|
+
// coming back is not a new arrival.
|
|
61577
|
+
return;
|
|
61578
|
+
}
|
|
61579
|
+
engagedRef.current = false;
|
|
61580
|
+
};
|
|
61581
|
+
|
|
61582
|
+
return { onMouseDown, onClick, onKeyDown, onBlur };
|
|
61583
|
+
};
|
|
61584
|
+
|
|
61472
61585
|
/**
|
|
61473
61586
|
* Input component for all textual input types.
|
|
61474
61587
|
*
|
|
@@ -61576,7 +61689,8 @@ const InputTextualUI = props => {
|
|
|
61576
61689
|
ui,
|
|
61577
61690
|
variant,
|
|
61578
61691
|
backgroundColor,
|
|
61579
|
-
width = "maxLength"
|
|
61692
|
+
width = "maxLength",
|
|
61693
|
+
selectOnPress
|
|
61580
61694
|
} = props;
|
|
61581
61695
|
const [inputControlRootProps, inputControlHostProps, controlChildrenWrapperProps] = useInputTextualProps(props);
|
|
61582
61696
|
const {
|
|
@@ -61618,6 +61732,8 @@ const InputTextualUI = props => {
|
|
|
61618
61732
|
} else {
|
|
61619
61733
|
inputControlHostProps.width = width;
|
|
61620
61734
|
}
|
|
61735
|
+
delete inputControlRootProps.selectOnPress;
|
|
61736
|
+
inputControlHostProps.selectOnPress = selectOnPress;
|
|
61621
61737
|
return jsxs(Box, {
|
|
61622
61738
|
as: "span",
|
|
61623
61739
|
inline: true,
|
|
@@ -61690,7 +61806,7 @@ const resolveWidthFromMaxLength = (maxLength, inputMode) => {
|
|
|
61690
61806
|
// rest — the guards, the slots, the constraints — which describe an edition
|
|
61691
61807
|
// that does not happen here. What survives is what a Box understands: width,
|
|
61692
61808
|
// 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"];
|
|
61809
|
+
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
61810
|
const InputTextualAsText = props => {
|
|
61695
61811
|
installInputCss();
|
|
61696
61812
|
// The id a Field handed down, which is what its Label points at.
|
|
@@ -61754,14 +61870,20 @@ const InputTextualFirstResolver = props => {
|
|
|
61754
61870
|
const InputTextual = /*#__PURE__*/createComponentResolver([InputTextualAsTextResolver, InputTextualFirstResolver, InputWithListResolver, InputWithSuggestionsResolver, InputTypeResolver, InputModeResolver, InputHeadlessResolver, InputTextualUI]);
|
|
61755
61871
|
const RealInput = ({
|
|
61756
61872
|
maxLength,
|
|
61873
|
+
selectOnPress,
|
|
61757
61874
|
...domProps
|
|
61758
61875
|
}) => {
|
|
61759
61876
|
const autoSelectReadOnlyProps = useAutoSelectReadOnly(domProps);
|
|
61877
|
+
const selectOnPressProps = useSelectOnPress({
|
|
61878
|
+
...domProps,
|
|
61879
|
+
...autoSelectReadOnlyProps
|
|
61880
|
+
}, selectOnPress);
|
|
61760
61881
|
return jsx(Box, {
|
|
61761
61882
|
...domProps,
|
|
61762
61883
|
as: "input",
|
|
61763
61884
|
baseClassName: "navi_control_input",
|
|
61764
61885
|
...autoSelectReadOnlyProps,
|
|
61886
|
+
...selectOnPressProps,
|
|
61765
61887
|
// Never set native maxLength — our guard handles it. Omitting it entirely
|
|
61766
61888
|
// avoids a Preact quirk: setting maxLength={undefined} on a fresh DOM element
|
|
61767
61889
|
// (e.g. after a Suspense remount) causes Preact to run `el.maxLength = ""`
|
|
@@ -77592,6 +77714,11 @@ const ListItemSelectable = props => {
|
|
|
77592
77714
|
...(hasSelectedProp ? {
|
|
77593
77715
|
checked: selected
|
|
77594
77716
|
} : null)
|
|
77717
|
+
},
|
|
77718
|
+
// The host is the visually hidden input carrying the selection: the row
|
|
77719
|
+
// is what the person sees and presses (see onClick above).
|
|
77720
|
+
{
|
|
77721
|
+
listenersOn: "root"
|
|
77595
77722
|
});
|
|
77596
77723
|
const {
|
|
77597
77724
|
checked,
|
|
@@ -86530,7 +86657,11 @@ const PickerButton = props => {
|
|
|
86530
86657
|
...props,
|
|
86531
86658
|
ref: inputRef
|
|
86532
86659
|
}, {
|
|
86533
|
-
controlType: "picker"
|
|
86660
|
+
controlType: "picker",
|
|
86661
|
+
// The root holds what the picker opens as well as its trigger: a
|
|
86662
|
+
// listener on the picker hears what bubbles out of the popup (the focus
|
|
86663
|
+
// leaving it, see picker_custom.jsx).
|
|
86664
|
+
listenersOn: "root"
|
|
86534
86665
|
});
|
|
86535
86666
|
const uiStateController = getUIStateControllerById(inputProps.id);
|
|
86536
86667
|
const value = uiStateController.uiState;
|
|
@@ -89739,6 +89870,8 @@ const InputDuration = props => {
|
|
|
89739
89870
|
}, {
|
|
89740
89871
|
controlType: "duration_group",
|
|
89741
89872
|
cascadeValidationToChildren: true,
|
|
89873
|
+
// The host is an <input type="hidden"> carrying the value.
|
|
89874
|
+
listenersOn: "root",
|
|
89742
89875
|
// Aggregates sub-input values into an ISO 8601-like duration string.
|
|
89743
89876
|
// durationToISOString preserves non-numeric mid-edit values
|
|
89744
89877
|
// (e.g. hours="ab") between their unit markers ("PTabH30M"),
|
|
@@ -89805,7 +89938,7 @@ const InputDuration = props => {
|
|
|
89805
89938
|
} = groupHostProps;
|
|
89806
89939
|
const groupRef = useRef();
|
|
89807
89940
|
useInputGroup(groupRef);
|
|
89808
|
-
const clipboardProps = useClipboardProps(groupRef);
|
|
89941
|
+
const clipboardProps = useClipboardProps(groupRef, groupRootProps);
|
|
89809
89942
|
const hourValue = components?.hours;
|
|
89810
89943
|
const minuteValue = components?.minutes;
|
|
89811
89944
|
const rawSecondValue = components?.seconds;
|
|
@@ -89878,7 +90011,11 @@ const InputDuration = props => {
|
|
|
89878
90011
|
const getVisibleFields = container => {
|
|
89879
90012
|
return ["hour", "minute", "second", "millisecond"].filter(f => container.querySelector(`[navi-input-type="${f}"]`) !== null);
|
|
89880
90013
|
};
|
|
89881
|
-
|
|
90014
|
+
|
|
90015
|
+
// Spread after the group's props, so the caller's listeners for these events
|
|
90016
|
+
// are called from here. Whatever they do, the group's own reading runs too, as
|
|
90017
|
+
// it does after a field's paste guard prevented the paste.
|
|
90018
|
+
const useClipboardProps = (groupRef, callerProps) => {
|
|
89882
90019
|
const getClipboardPayload = () => {
|
|
89883
90020
|
const groupEl = groupRef.current;
|
|
89884
90021
|
if (!groupEl) {
|
|
@@ -89944,6 +90081,7 @@ const useClipboardProps = groupRef => {
|
|
|
89944
90081
|
return true;
|
|
89945
90082
|
};
|
|
89946
90083
|
const onCopy = e => {
|
|
90084
|
+
callerProps.onCopy?.(e);
|
|
89947
90085
|
if (!isFromDurationField(e)) {
|
|
89948
90086
|
return;
|
|
89949
90087
|
}
|
|
@@ -89956,6 +90094,7 @@ const useClipboardProps = groupRef => {
|
|
|
89956
90094
|
e.preventDefault();
|
|
89957
90095
|
};
|
|
89958
90096
|
const onCut = e => {
|
|
90097
|
+
callerProps.onCut?.(e);
|
|
89959
90098
|
if (!isFromDurationField(e)) {
|
|
89960
90099
|
return;
|
|
89961
90100
|
}
|
|
@@ -89968,6 +90107,7 @@ const useClipboardProps = groupRef => {
|
|
|
89968
90107
|
applyToGroup("", e);
|
|
89969
90108
|
};
|
|
89970
90109
|
const onPaste = e => {
|
|
90110
|
+
callerProps.onPaste?.(e);
|
|
89971
90111
|
if (!isFromDurationField(e)) {
|
|
89972
90112
|
return;
|
|
89973
90113
|
}
|
|
@@ -92537,7 +92677,10 @@ function WheelUI(props) {
|
|
|
92537
92677
|
// action explicitly on settle. A caller can still override actionEvent.
|
|
92538
92678
|
actionEvent: props.actionEvent ?? "custom"
|
|
92539
92679
|
}, {
|
|
92540
|
-
controlType: "input"
|
|
92680
|
+
controlType: "input",
|
|
92681
|
+
// The host is the hidden input carrying the value: the container is
|
|
92682
|
+
// what is focused and spun.
|
|
92683
|
+
listenersOn: "root"
|
|
92541
92684
|
});
|
|
92542
92685
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
92543
92686
|
const {
|