@jsenv/navi 0.29.453 → 0.29.455
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 +111 -65
- package/dist/dev/jsenv_navi.js.map +9 -7
- package/dist/jsenv_navi.js +108 -64
- package/dist/jsenv_navi.js.map +9 -7
- package/docs/control_value.md +18 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -39577,6 +39577,59 @@ const callLiftOverCallbacks = () => {
|
|
|
39577
39577
|
});
|
|
39578
39578
|
};
|
|
39579
39579
|
|
|
39580
|
+
/**
|
|
39581
|
+
* What a gesture changes in a closed picker is sent at once.
|
|
39582
|
+
*
|
|
39583
|
+
* A picker's `action` waits for its popup to close: what is done in there is
|
|
39584
|
+
* one answer, given when the person is done. A change made while the popup is
|
|
39585
|
+
* closed — the clear cross, a chip's cross, a paste, a cut, a value proposed
|
|
39586
|
+
* from beside it (`--navi-update`) — has no close coming, so nothing would ever
|
|
39587
|
+
* send it: the value moves on screen and the record behind it stays. Each of
|
|
39588
|
+
* those gestures calls this right after setting the value; a failing action
|
|
39589
|
+
* puts the value back (resetOnError), as it does after a close.
|
|
39590
|
+
*
|
|
39591
|
+
* Called by gestures only. A value set without one — a bound signal followed, a
|
|
39592
|
+
* cancel or a failure rolled back — is the picker being told, not asked, which
|
|
39593
|
+
* is why this is not a reaction to every value set on a closed picker.
|
|
39594
|
+
*
|
|
39595
|
+
* The action is requested the way the close requests it, not through
|
|
39596
|
+
* `--navi-send`: a send also answers the surface around the control (a slide
|
|
39597
|
+
* moves on, an open dialog closes — see resolveAfterSend in commands.js), and a
|
|
39598
|
+
* cross pressed on a picker in a dialog is not the dialog being answered.
|
|
39599
|
+
*/
|
|
39600
|
+
|
|
39601
|
+
|
|
39602
|
+
const sendClosedPickerChange = (element, { event, name, requester }) => {
|
|
39603
|
+
const controlHost = findControlHost(element) || element;
|
|
39604
|
+
const controlRoot = findControlRoot(controlHost);
|
|
39605
|
+
if (!controlRoot || controlRoot.getAttribute("navi-control") !== "picker") {
|
|
39606
|
+
return;
|
|
39607
|
+
}
|
|
39608
|
+
if (controlRoot.getAttribute("aria-expanded") === "true") {
|
|
39609
|
+
return;
|
|
39610
|
+
}
|
|
39611
|
+
const { props } = controlHost.__uiStateController__;
|
|
39612
|
+
if (!props.action) {
|
|
39613
|
+
return;
|
|
39614
|
+
}
|
|
39615
|
+
// A picker that picks nothing (`type="confirm"`, a menu of actions) holds no
|
|
39616
|
+
// value its action would be sending: that action is the yes of its popup,
|
|
39617
|
+
// and a paste landing on the trigger must not say it.
|
|
39618
|
+
if (props.picksNothing) {
|
|
39619
|
+
return;
|
|
39620
|
+
}
|
|
39621
|
+
// A source with an action of its own made the request already: `<Button
|
|
39622
|
+
// action={remove} command="--navi-clear">` runs the removal, and the command
|
|
39623
|
+
// that follows only brings the picker in line with it. Sending the picker's
|
|
39624
|
+
// action on top would ask twice.
|
|
39625
|
+
if (requester?.__uiStateController__?.props.action) {
|
|
39626
|
+
return;
|
|
39627
|
+
}
|
|
39628
|
+
// `requester`: the source of a command may have claimed the press to send
|
|
39629
|
+
// this very change (see isAimedAtSelfInteractionsBelow).
|
|
39630
|
+
dispatchRequestAction(controlHost, { event, name, requester });
|
|
39631
|
+
};
|
|
39632
|
+
|
|
39580
39633
|
/**
|
|
39581
39634
|
* Runs a navi command from JS. It is the LAST RESORT: the attributes say the
|
|
39582
39635
|
* same thing declaratively and go through this very function, so a press that
|
|
@@ -39952,6 +40005,11 @@ registerNaviCommand("--navi-update", (source, event, { argument }) => {
|
|
|
39952
40005
|
event,
|
|
39953
40006
|
behavior: argument,
|
|
39954
40007
|
});
|
|
40008
|
+
sendClosedPickerChange(target, {
|
|
40009
|
+
event,
|
|
40010
|
+
name: "--navi-update",
|
|
40011
|
+
requester: source,
|
|
40012
|
+
});
|
|
39955
40013
|
},
|
|
39956
40014
|
});
|
|
39957
40015
|
},
|
|
@@ -39964,20 +40022,6 @@ registerNaviCommand("--navi-clear", (source, event) => {
|
|
|
39964
40022
|
return undefined;
|
|
39965
40023
|
}
|
|
39966
40024
|
const fromInput = source.closest(`[navi-control="input"]`);
|
|
39967
|
-
// A control that commits on an explicit send — a picker, whose list sends the
|
|
39968
|
-
// moment a value is chosen — has nothing that would commit a clear: its
|
|
39969
|
-
// action never runs on a ui state change. Left alone, the field goes empty
|
|
39970
|
-
// while the caller still holds the value it gave, and renders it right back.
|
|
39971
|
-
//
|
|
39972
|
-
// Unless the source committed the clear itself: a clear button given its own
|
|
39973
|
-
// action (`<Button action={remove} command="--navi-clear">`) already made the
|
|
39974
|
-
// request — the ui state is being brought in line with what just happened, and
|
|
39975
|
-
// sending the picker's own action on top would ask twice.
|
|
39976
|
-
const sourceController = source.__uiStateController__;
|
|
39977
|
-
const fromSendOnlyControl = Boolean(
|
|
39978
|
-
source.closest?.(`[navi-control=picker]`) &&
|
|
39979
|
-
!sourceController?.props.action,
|
|
39980
|
-
);
|
|
39981
40025
|
|
|
39982
40026
|
const performClear = (clearEvent) => {
|
|
39983
40027
|
dispatchRequestInteraction(target, {
|
|
@@ -39988,46 +40032,11 @@ registerNaviCommand("--navi-clear", (source, event) => {
|
|
|
39988
40032
|
requester: source,
|
|
39989
40033
|
prevented: () => clearEvent.preventDefault(),
|
|
39990
40034
|
allowed: () => {
|
|
39991
|
-
// What the control holds, before it holds nothing: the clear is
|
|
39992
|
-
// optimistic — the field empties now and the send that commits it may
|
|
39993
|
-
// still fail — so what it emptied has to be kept to be put back.
|
|
39994
|
-
const uiStateBefore = getUIStateFromElement(target);
|
|
39995
40035
|
dispatchRequestClearUIState(target, clearEvent);
|
|
39996
|
-
|
|
39997
|
-
|
|
39998
|
-
|
|
39999
|
-
|
|
40000
|
-
// state signal, so this sends the value the control now holds.
|
|
40001
|
-
const actionHost = findControlHost(target) || target;
|
|
40002
|
-
const completion = watchActionCompletion(actionHost, () => {
|
|
40003
|
-
triggerNaviCommand(source, "--navi-send", clearEvent, {
|
|
40004
|
-
optional: true,
|
|
40005
|
-
});
|
|
40006
|
-
});
|
|
40007
|
-
completion.whenSettled(({ error, aborted }) => {
|
|
40008
|
-
if (!error && !aborted) {
|
|
40009
|
-
return;
|
|
40010
|
-
}
|
|
40011
|
-
// The removal did not happen, so the field must stop saying it did.
|
|
40012
|
-
// The error itself stays where the action put it — on the control,
|
|
40013
|
-
// which is still there, unlike the cross that has just gone with the
|
|
40014
|
-
// value it cleared (see addErrorMessage in use_execute_action.js).
|
|
40015
|
-
//
|
|
40016
|
-
// Put back from the inside ("clear_rollback" is an internal event
|
|
40017
|
-
// type, see ui_state_controller.js) rather than asked for the way a
|
|
40018
|
-
// user would: nobody acted, the control is being returned to the
|
|
40019
|
-
// state its caller still holds. Asked from the outside it would be
|
|
40020
|
-
// answered with "this element is busy" — the action is still
|
|
40021
|
-
// settling — over the very error that explains why the value is back.
|
|
40022
|
-
const controller = actionHost.__uiStateController__;
|
|
40023
|
-
if (!controller) {
|
|
40024
|
-
return;
|
|
40025
|
-
}
|
|
40026
|
-
const rollbackEvent = new CustomEvent("clear_rollback", {
|
|
40027
|
-
detail: {},
|
|
40028
|
-
});
|
|
40029
|
-
chainEvent(rollbackEvent, clearEvent);
|
|
40030
|
-
controller.setUIState(uiStateBefore, rollbackEvent);
|
|
40036
|
+
sendClosedPickerChange(target, {
|
|
40037
|
+
event: clearEvent,
|
|
40038
|
+
name: "--navi-clear",
|
|
40039
|
+
requester: source,
|
|
40031
40040
|
});
|
|
40032
40041
|
},
|
|
40033
40042
|
});
|
|
@@ -40767,6 +40776,7 @@ registerNaviCommand("--navi-select", (source, event) => {
|
|
|
40767
40776
|
return dispatchCustomEvent(target, "navi_request_select", {
|
|
40768
40777
|
event,
|
|
40769
40778
|
id: resolveCommandValue(source, event),
|
|
40779
|
+
requester: source,
|
|
40770
40780
|
});
|
|
40771
40781
|
},
|
|
40772
40782
|
};
|
|
@@ -40783,6 +40793,7 @@ registerNaviCommand("--navi-unselect", (source, event) => {
|
|
|
40783
40793
|
return dispatchCustomEvent(target, "navi_request_unselect", {
|
|
40784
40794
|
event,
|
|
40785
40795
|
id: resolveCommandValue(source, event),
|
|
40796
|
+
requester: source,
|
|
40786
40797
|
});
|
|
40787
40798
|
},
|
|
40788
40799
|
};
|
|
@@ -44623,12 +44634,6 @@ const INTERNAL_EVENT_SET = new Set([
|
|
|
44623
44634
|
// on registration, and group pushing value/defaultValue to children on registerChild.
|
|
44624
44635
|
// Equivalent to defaultValue initialization: no uiAction, no commands, no parent notification.
|
|
44625
44636
|
"initial_state_push",
|
|
44626
|
-
// navi undoing its own optimistic write: the clear cross emptied the control
|
|
44627
|
-
// before the send that commits it, the send failed, and the value it emptied
|
|
44628
|
-
// goes back where it was (see the --navi-clear command). Nothing acted — the
|
|
44629
|
-
// control is being put back on the state the caller still holds — so this
|
|
44630
|
-
// must not fire a uiAction, a command, or a report on the way.
|
|
44631
|
-
"clear_rollback",
|
|
44632
44637
|
// A picker's popup closed on a cancel (the no button, Escape, a click
|
|
44633
44638
|
// outside): what the picker held at open goes back. Nobody acted — no
|
|
44634
44639
|
// command — but the value really did move back, so uiAction and the parent
|
|
@@ -50859,11 +50864,15 @@ const LinkWithRoute = ({
|
|
|
50859
50864
|
also: currentAlso
|
|
50860
50865
|
});
|
|
50861
50866
|
const innerCurrent = current || linkMatching;
|
|
50867
|
+
|
|
50868
|
+
// What a childless link shows is LinkPlain's call (hrefFallback); a route
|
|
50869
|
+
// only says it the way its pattern writes it, not as the resolved href.
|
|
50862
50870
|
return jsx(Link, {
|
|
50863
50871
|
href: url,
|
|
50872
|
+
hrefText: route.buildRelativeUrl(routeParams),
|
|
50864
50873
|
current: innerCurrent,
|
|
50865
50874
|
...rest,
|
|
50866
|
-
children: children
|
|
50875
|
+
children: children
|
|
50867
50876
|
});
|
|
50868
50877
|
};
|
|
50869
50878
|
const LinkPlain = props => {
|
|
@@ -50889,6 +50898,7 @@ const LinkPlain = props => {
|
|
|
50889
50898
|
revealOnInteraction = false,
|
|
50890
50899
|
stretch,
|
|
50891
50900
|
hrefFallback = !anchor,
|
|
50901
|
+
hrefText = href,
|
|
50892
50902
|
routeTransition,
|
|
50893
50903
|
pressableDuringRouteTransition,
|
|
50894
50904
|
replace,
|
|
@@ -51024,7 +51034,7 @@ const LinkPlain = props => {
|
|
|
51024
51034
|
const documentRequest = isDocument ? {
|
|
51025
51035
|
[LINK_DOCUMENT_ATTRIBUTE]: ""
|
|
51026
51036
|
} : null;
|
|
51027
|
-
const innerChildren = children || (hrefFallback ?
|
|
51037
|
+
const innerChildren = children || (hrefFallback ? hrefText : children);
|
|
51028
51038
|
const startIconEl = startIcon;
|
|
51029
51039
|
const endIconEl = innerEndIcon;
|
|
51030
51040
|
|
|
@@ -51075,6 +51085,7 @@ const LinkPlain = props => {
|
|
|
51075
51085
|
startIcon: undefined,
|
|
51076
51086
|
endIcon: undefined,
|
|
51077
51087
|
hrefFallback: undefined,
|
|
51088
|
+
hrefText: undefined,
|
|
51078
51089
|
routeTransition: undefined,
|
|
51079
51090
|
pressableDuringRouteTransition: undefined,
|
|
51080
51091
|
replace: undefined,
|
|
@@ -81179,7 +81190,9 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
|
|
|
81179
81190
|
/**
|
|
81180
81191
|
* One value the picker holds, drawn as a chip with a cross that takes it back
|
|
81181
81192
|
* out. Sits wherever the application draws what was picked — on the picker's
|
|
81182
|
-
* façade (`ui`) or inside its popup
|
|
81193
|
+
* façade (`ui`) or inside its popup. Inside the open popup the removal waits
|
|
81194
|
+
* for the close like any change made there; pressed while the popup is closed
|
|
81195
|
+
* it is the whole gesture, and the picker's `action` runs at once.
|
|
81183
81196
|
*
|
|
81184
81197
|
* The cross asks with `--navi-unselect` rather than writing a new list, and it
|
|
81185
81198
|
* asks the picker — which holds what was picked, and hands it down to whatever
|
|
@@ -82207,15 +82220,25 @@ const PickerButton = props => {
|
|
|
82207
82220
|
const displayText = pickerEl.querySelector(".navi_picker_value")?.textContent ?? String(uiState);
|
|
82208
82221
|
e.clipboardData.setData("text/plain", displayText);
|
|
82209
82222
|
e.clipboardData.setData("application/x-navi", JSON.stringify(uiState));
|
|
82223
|
+
e.preventDefault();
|
|
82224
|
+
// A picker that asks before its cross clears anything is not
|
|
82225
|
+
// emptied by a keystroke without the question: the cut copies,
|
|
82226
|
+
// and the value stays.
|
|
82227
|
+
if (clearConfirm !== undefined) {
|
|
82228
|
+
return;
|
|
82229
|
+
}
|
|
82210
82230
|
// the clear ui state part need control to be interactable
|
|
82211
82231
|
dispatchRequestInteraction(pickerEl, {
|
|
82212
82232
|
event: e,
|
|
82213
82233
|
name: "cut",
|
|
82214
82234
|
allowed: () => {
|
|
82215
82235
|
dispatchRequestClearUIState(inputRef.current, e);
|
|
82236
|
+
sendClosedPickerChange(inputRef.current, {
|
|
82237
|
+
event: e,
|
|
82238
|
+
name: "cut"
|
|
82239
|
+
});
|
|
82216
82240
|
}
|
|
82217
82241
|
});
|
|
82218
|
-
e.preventDefault();
|
|
82219
82242
|
},
|
|
82220
82243
|
onPaste: e => {
|
|
82221
82244
|
const pickerEl = ref.current;
|
|
@@ -82241,6 +82264,10 @@ const PickerButton = props => {
|
|
|
82241
82264
|
dispatchRequestSetUIState(inputRef.current, pasteValue, {
|
|
82242
82265
|
event: e
|
|
82243
82266
|
});
|
|
82267
|
+
sendClosedPickerChange(inputRef.current, {
|
|
82268
|
+
event: e,
|
|
82269
|
+
name: "paste"
|
|
82270
|
+
});
|
|
82244
82271
|
}
|
|
82245
82272
|
});
|
|
82246
82273
|
e.preventDefault();
|
|
@@ -82459,7 +82486,8 @@ const requestPickerListEntry = (pickerEl, pickerInputEl, e, goal) => {
|
|
|
82459
82486
|
return;
|
|
82460
82487
|
}
|
|
82461
82488
|
const {
|
|
82462
|
-
id: entry
|
|
82489
|
+
id: entry,
|
|
82490
|
+
requester
|
|
82463
82491
|
} = e.detail;
|
|
82464
82492
|
const isThere = uiState.some(item => compareTwoJsValues(item, entry));
|
|
82465
82493
|
if (goal === "select" ? isThere : !isThere) {
|
|
@@ -82474,6 +82502,11 @@ const requestPickerListEntry = (pickerEl, pickerInputEl, e, goal) => {
|
|
|
82474
82502
|
dispatchRequestSetUIState(pickerInputEl, uiStateNext, {
|
|
82475
82503
|
event: e
|
|
82476
82504
|
});
|
|
82505
|
+
sendClosedPickerChange(pickerInputEl, {
|
|
82506
|
+
event: e,
|
|
82507
|
+
name: goal,
|
|
82508
|
+
requester
|
|
82509
|
+
});
|
|
82477
82510
|
}
|
|
82478
82511
|
});
|
|
82479
82512
|
};
|
|
@@ -93963,6 +93996,10 @@ const css$5 = /* css */`@keyframes navi_image_shimmer {
|
|
|
93963
93996
|
`;
|
|
93964
93997
|
const DEFAULT_PLACEHOLDER_LIGHT = "#d4d8dd";
|
|
93965
93998
|
const DEFAULT_PLACEHOLDER_DARK = "#374151";
|
|
93999
|
+
// On an <img>, width/height describe the image: they stay HTML attributes.
|
|
94000
|
+
// Box would write them as inline styles, which beat every stylesheet rule, so
|
|
94001
|
+
// an image given its file's size (3840×2160) would overflow the box sizing it.
|
|
94002
|
+
const IMAGE_ATTRIBUTE_PROP_SET = new Set(["width", "height"]);
|
|
93966
94003
|
|
|
93967
94004
|
/**
|
|
93968
94005
|
* The props may come in any order: `src` and `srcset` reach the element after
|
|
@@ -93971,6 +94008,12 @@ const DEFAULT_PLACEHOLDER_DARK = "#374151";
|
|
|
93971
94008
|
* decides whether to load when `src` is set, and an image whose `src` came
|
|
93972
94009
|
* first loads, lazy or not.
|
|
93973
94010
|
*
|
|
94011
|
+
* @param {number} [props.width] - The image's width in pixels, written as the
|
|
94012
|
+
* `<img>` attribute, like `height`. Given both, the browser reserves the
|
|
94013
|
+
* aspect ratio before the bytes arrive. They size the element only while no
|
|
94014
|
+
* stylesheet rule does: a box that holds the image sizes it with CSS (or
|
|
94015
|
+
* `style`) and keeps the ratio.
|
|
94016
|
+
* @param {number} [props.height] - The image's height in pixels, see `width`.
|
|
93974
94017
|
* @param {string|false} [props.placeholderColor] - Background color shown while the image loads.
|
|
93975
94018
|
* Defaults to a light gray. Pass `false` to disable.
|
|
93976
94019
|
* @param {boolean} [props.placeholderDark] - Use a dark default placeholder color,
|
|
@@ -93995,6 +94038,7 @@ const Image = ({
|
|
|
93995
94038
|
...rest,
|
|
93996
94039
|
as: "img",
|
|
93997
94040
|
baseClassName: "navi_image",
|
|
94041
|
+
childPropSet: IMAGE_ATTRIBUTE_PROP_SET,
|
|
93998
94042
|
"navi-placeholder": loadedRef.current ? undefined : "",
|
|
93999
94043
|
style: {
|
|
94000
94044
|
"--placeholder-color": resolvedPlaceholder || undefined,
|