@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/dev/jsenv_navi.js
CHANGED
|
@@ -40608,6 +40608,59 @@ const callLiftOverCallbacks = () => {
|
|
|
40608
40608
|
});
|
|
40609
40609
|
};
|
|
40610
40610
|
|
|
40611
|
+
/**
|
|
40612
|
+
* What a gesture changes in a closed picker is sent at once.
|
|
40613
|
+
*
|
|
40614
|
+
* A picker's `action` waits for its popup to close: what is done in there is
|
|
40615
|
+
* one answer, given when the person is done. A change made while the popup is
|
|
40616
|
+
* closed — the clear cross, a chip's cross, a paste, a cut, a value proposed
|
|
40617
|
+
* from beside it (`--navi-update`) — has no close coming, so nothing would ever
|
|
40618
|
+
* send it: the value moves on screen and the record behind it stays. Each of
|
|
40619
|
+
* those gestures calls this right after setting the value; a failing action
|
|
40620
|
+
* puts the value back (resetOnError), as it does after a close.
|
|
40621
|
+
*
|
|
40622
|
+
* Called by gestures only. A value set without one — a bound signal followed, a
|
|
40623
|
+
* cancel or a failure rolled back — is the picker being told, not asked, which
|
|
40624
|
+
* is why this is not a reaction to every value set on a closed picker.
|
|
40625
|
+
*
|
|
40626
|
+
* The action is requested the way the close requests it, not through
|
|
40627
|
+
* `--navi-send`: a send also answers the surface around the control (a slide
|
|
40628
|
+
* moves on, an open dialog closes — see resolveAfterSend in commands.js), and a
|
|
40629
|
+
* cross pressed on a picker in a dialog is not the dialog being answered.
|
|
40630
|
+
*/
|
|
40631
|
+
|
|
40632
|
+
|
|
40633
|
+
const sendClosedPickerChange = (element, { event, name, requester }) => {
|
|
40634
|
+
const controlHost = findControlHost(element) || element;
|
|
40635
|
+
const controlRoot = findControlRoot(controlHost);
|
|
40636
|
+
if (!controlRoot || controlRoot.getAttribute("navi-control") !== "picker") {
|
|
40637
|
+
return;
|
|
40638
|
+
}
|
|
40639
|
+
if (controlRoot.getAttribute("aria-expanded") === "true") {
|
|
40640
|
+
return;
|
|
40641
|
+
}
|
|
40642
|
+
const { props } = controlHost.__uiStateController__;
|
|
40643
|
+
if (!props.action) {
|
|
40644
|
+
return;
|
|
40645
|
+
}
|
|
40646
|
+
// A picker that picks nothing (`type="confirm"`, a menu of actions) holds no
|
|
40647
|
+
// value its action would be sending: that action is the yes of its popup,
|
|
40648
|
+
// and a paste landing on the trigger must not say it.
|
|
40649
|
+
if (props.picksNothing) {
|
|
40650
|
+
return;
|
|
40651
|
+
}
|
|
40652
|
+
// A source with an action of its own made the request already: `<Button
|
|
40653
|
+
// action={remove} command="--navi-clear">` runs the removal, and the command
|
|
40654
|
+
// that follows only brings the picker in line with it. Sending the picker's
|
|
40655
|
+
// action on top would ask twice.
|
|
40656
|
+
if (requester?.__uiStateController__?.props.action) {
|
|
40657
|
+
return;
|
|
40658
|
+
}
|
|
40659
|
+
// `requester`: the source of a command may have claimed the press to send
|
|
40660
|
+
// this very change (see isAimedAtSelfInteractionsBelow).
|
|
40661
|
+
dispatchRequestAction(controlHost, { event, name, requester });
|
|
40662
|
+
};
|
|
40663
|
+
|
|
40611
40664
|
/**
|
|
40612
40665
|
* Runs a navi command from JS. It is the LAST RESORT: the attributes say the
|
|
40613
40666
|
* same thing declaratively and go through this very function, so a press that
|
|
@@ -40999,6 +41052,11 @@ registerNaviCommand("--navi-update", (source, event, { argument }) => {
|
|
|
40999
41052
|
event,
|
|
41000
41053
|
behavior: argument,
|
|
41001
41054
|
});
|
|
41055
|
+
sendClosedPickerChange(target, {
|
|
41056
|
+
event,
|
|
41057
|
+
name: "--navi-update",
|
|
41058
|
+
requester: source,
|
|
41059
|
+
});
|
|
41002
41060
|
},
|
|
41003
41061
|
});
|
|
41004
41062
|
},
|
|
@@ -41011,20 +41069,6 @@ registerNaviCommand("--navi-clear", (source, event) => {
|
|
|
41011
41069
|
return undefined;
|
|
41012
41070
|
}
|
|
41013
41071
|
const fromInput = source.closest(`[navi-control="input"]`);
|
|
41014
|
-
// A control that commits on an explicit send — a picker, whose list sends the
|
|
41015
|
-
// moment a value is chosen — has nothing that would commit a clear: its
|
|
41016
|
-
// action never runs on a ui state change. Left alone, the field goes empty
|
|
41017
|
-
// while the caller still holds the value it gave, and renders it right back.
|
|
41018
|
-
//
|
|
41019
|
-
// Unless the source committed the clear itself: a clear button given its own
|
|
41020
|
-
// action (`<Button action={remove} command="--navi-clear">`) already made the
|
|
41021
|
-
// request — the ui state is being brought in line with what just happened, and
|
|
41022
|
-
// sending the picker's own action on top would ask twice.
|
|
41023
|
-
const sourceController = source.__uiStateController__;
|
|
41024
|
-
const fromSendOnlyControl = Boolean(
|
|
41025
|
-
source.closest?.(`[navi-control=picker]`) &&
|
|
41026
|
-
!sourceController?.props.action,
|
|
41027
|
-
);
|
|
41028
41072
|
|
|
41029
41073
|
const performClear = (clearEvent) => {
|
|
41030
41074
|
dispatchRequestInteraction(target, {
|
|
@@ -41035,46 +41079,11 @@ registerNaviCommand("--navi-clear", (source, event) => {
|
|
|
41035
41079
|
requester: source,
|
|
41036
41080
|
prevented: () => clearEvent.preventDefault(),
|
|
41037
41081
|
allowed: () => {
|
|
41038
|
-
// What the control holds, before it holds nothing: the clear is
|
|
41039
|
-
// optimistic — the field empties now and the send that commits it may
|
|
41040
|
-
// still fail — so what it emptied has to be kept to be put back.
|
|
41041
|
-
const uiStateBefore = getUIStateFromElement(target);
|
|
41042
41082
|
dispatchRequestClearUIState(target, clearEvent);
|
|
41043
|
-
|
|
41044
|
-
|
|
41045
|
-
|
|
41046
|
-
|
|
41047
|
-
// state signal, so this sends the value the control now holds.
|
|
41048
|
-
const actionHost = findControlHost(target) || target;
|
|
41049
|
-
const completion = watchActionCompletion(actionHost, () => {
|
|
41050
|
-
triggerNaviCommand(source, "--navi-send", clearEvent, {
|
|
41051
|
-
optional: true,
|
|
41052
|
-
});
|
|
41053
|
-
});
|
|
41054
|
-
completion.whenSettled(({ error, aborted }) => {
|
|
41055
|
-
if (!error && !aborted) {
|
|
41056
|
-
return;
|
|
41057
|
-
}
|
|
41058
|
-
// The removal did not happen, so the field must stop saying it did.
|
|
41059
|
-
// The error itself stays where the action put it — on the control,
|
|
41060
|
-
// which is still there, unlike the cross that has just gone with the
|
|
41061
|
-
// value it cleared (see addErrorMessage in use_execute_action.js).
|
|
41062
|
-
//
|
|
41063
|
-
// Put back from the inside ("clear_rollback" is an internal event
|
|
41064
|
-
// type, see ui_state_controller.js) rather than asked for the way a
|
|
41065
|
-
// user would: nobody acted, the control is being returned to the
|
|
41066
|
-
// state its caller still holds. Asked from the outside it would be
|
|
41067
|
-
// answered with "this element is busy" — the action is still
|
|
41068
|
-
// settling — over the very error that explains why the value is back.
|
|
41069
|
-
const controller = actionHost.__uiStateController__;
|
|
41070
|
-
if (!controller) {
|
|
41071
|
-
return;
|
|
41072
|
-
}
|
|
41073
|
-
const rollbackEvent = new CustomEvent("clear_rollback", {
|
|
41074
|
-
detail: {},
|
|
41075
|
-
});
|
|
41076
|
-
chainEvent(rollbackEvent, clearEvent);
|
|
41077
|
-
controller.setUIState(uiStateBefore, rollbackEvent);
|
|
41083
|
+
sendClosedPickerChange(target, {
|
|
41084
|
+
event: clearEvent,
|
|
41085
|
+
name: "--navi-clear",
|
|
41086
|
+
requester: source,
|
|
41078
41087
|
});
|
|
41079
41088
|
},
|
|
41080
41089
|
});
|
|
@@ -41814,6 +41823,7 @@ registerNaviCommand("--navi-select", (source, event) => {
|
|
|
41814
41823
|
return dispatchCustomEvent(target, "navi_request_select", {
|
|
41815
41824
|
event,
|
|
41816
41825
|
id: resolveCommandValue(source, event),
|
|
41826
|
+
requester: source,
|
|
41817
41827
|
});
|
|
41818
41828
|
},
|
|
41819
41829
|
};
|
|
@@ -41830,6 +41840,7 @@ registerNaviCommand("--navi-unselect", (source, event) => {
|
|
|
41830
41840
|
return dispatchCustomEvent(target, "navi_request_unselect", {
|
|
41831
41841
|
event,
|
|
41832
41842
|
id: resolveCommandValue(source, event),
|
|
41843
|
+
requester: source,
|
|
41833
41844
|
});
|
|
41834
41845
|
},
|
|
41835
41846
|
};
|
|
@@ -45733,12 +45744,6 @@ const INTERNAL_EVENT_SET = new Set([
|
|
|
45733
45744
|
// on registration, and group pushing value/defaultValue to children on registerChild.
|
|
45734
45745
|
// Equivalent to defaultValue initialization: no uiAction, no commands, no parent notification.
|
|
45735
45746
|
"initial_state_push",
|
|
45736
|
-
// navi undoing its own optimistic write: the clear cross emptied the control
|
|
45737
|
-
// before the send that commits it, the send failed, and the value it emptied
|
|
45738
|
-
// goes back where it was (see the --navi-clear command). Nothing acted — the
|
|
45739
|
-
// control is being put back on the state the caller still holds — so this
|
|
45740
|
-
// must not fire a uiAction, a command, or a report on the way.
|
|
45741
|
-
"clear_rollback",
|
|
45742
45747
|
// A picker's popup closed on a cancel (the no button, Escape, a click
|
|
45743
45748
|
// outside): what the picker held at open goes back. Nobody acted — no
|
|
45744
45749
|
// command — but the value really did move back, so uiAction and the parent
|
|
@@ -52062,11 +52067,15 @@ const LinkWithRoute = ({
|
|
|
52062
52067
|
also: currentAlso
|
|
52063
52068
|
});
|
|
52064
52069
|
const innerCurrent = current || linkMatching;
|
|
52070
|
+
|
|
52071
|
+
// What a childless link shows is LinkPlain's call (hrefFallback); a route
|
|
52072
|
+
// only says it the way its pattern writes it, not as the resolved href.
|
|
52065
52073
|
return jsx(Link, {
|
|
52066
52074
|
href: url,
|
|
52075
|
+
hrefText: route.buildRelativeUrl(routeParams),
|
|
52067
52076
|
current: innerCurrent,
|
|
52068
52077
|
...rest,
|
|
52069
|
-
children: children
|
|
52078
|
+
children: children
|
|
52070
52079
|
});
|
|
52071
52080
|
};
|
|
52072
52081
|
const LinkPlain = props => {
|
|
@@ -52092,6 +52101,7 @@ const LinkPlain = props => {
|
|
|
52092
52101
|
revealOnInteraction = false,
|
|
52093
52102
|
stretch,
|
|
52094
52103
|
hrefFallback = !anchor,
|
|
52104
|
+
hrefText = href,
|
|
52095
52105
|
routeTransition,
|
|
52096
52106
|
pressableDuringRouteTransition,
|
|
52097
52107
|
replace,
|
|
@@ -52227,7 +52237,7 @@ const LinkPlain = props => {
|
|
|
52227
52237
|
const documentRequest = isDocument ? {
|
|
52228
52238
|
[LINK_DOCUMENT_ATTRIBUTE]: ""
|
|
52229
52239
|
} : null;
|
|
52230
|
-
const innerChildren = children || (hrefFallback ?
|
|
52240
|
+
const innerChildren = children || (hrefFallback ? hrefText : children);
|
|
52231
52241
|
const startIconEl = startIcon;
|
|
52232
52242
|
const endIconEl = innerEndIcon;
|
|
52233
52243
|
|
|
@@ -52278,6 +52288,7 @@ const LinkPlain = props => {
|
|
|
52278
52288
|
startIcon: undefined,
|
|
52279
52289
|
endIcon: undefined,
|
|
52280
52290
|
hrefFallback: undefined,
|
|
52291
|
+
hrefText: undefined,
|
|
52281
52292
|
routeTransition: undefined,
|
|
52282
52293
|
pressableDuringRouteTransition: undefined,
|
|
52283
52294
|
replace: undefined,
|
|
@@ -82760,7 +82771,9 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
|
|
|
82760
82771
|
/**
|
|
82761
82772
|
* One value the picker holds, drawn as a chip with a cross that takes it back
|
|
82762
82773
|
* out. Sits wherever the application draws what was picked — on the picker's
|
|
82763
|
-
* façade (`ui`) or inside its popup
|
|
82774
|
+
* façade (`ui`) or inside its popup. Inside the open popup the removal waits
|
|
82775
|
+
* for the close like any change made there; pressed while the popup is closed
|
|
82776
|
+
* it is the whole gesture, and the picker's `action` runs at once.
|
|
82764
82777
|
*
|
|
82765
82778
|
* The cross asks with `--navi-unselect` rather than writing a new list, and it
|
|
82766
82779
|
* asks the picker — which holds what was picked, and hands it down to whatever
|
|
@@ -83792,15 +83805,25 @@ const PickerButton = props => {
|
|
|
83792
83805
|
const displayText = pickerEl.querySelector(".navi_picker_value")?.textContent ?? String(uiState);
|
|
83793
83806
|
e.clipboardData.setData("text/plain", displayText);
|
|
83794
83807
|
e.clipboardData.setData("application/x-navi", JSON.stringify(uiState));
|
|
83808
|
+
e.preventDefault();
|
|
83809
|
+
// A picker that asks before its cross clears anything is not
|
|
83810
|
+
// emptied by a keystroke without the question: the cut copies,
|
|
83811
|
+
// and the value stays.
|
|
83812
|
+
if (clearConfirm !== undefined) {
|
|
83813
|
+
return;
|
|
83814
|
+
}
|
|
83795
83815
|
// the clear ui state part need control to be interactable
|
|
83796
83816
|
dispatchRequestInteraction(pickerEl, {
|
|
83797
83817
|
event: e,
|
|
83798
83818
|
name: "cut",
|
|
83799
83819
|
allowed: () => {
|
|
83800
83820
|
dispatchRequestClearUIState(inputRef.current, e);
|
|
83821
|
+
sendClosedPickerChange(inputRef.current, {
|
|
83822
|
+
event: e,
|
|
83823
|
+
name: "cut"
|
|
83824
|
+
});
|
|
83801
83825
|
}
|
|
83802
83826
|
});
|
|
83803
|
-
e.preventDefault();
|
|
83804
83827
|
},
|
|
83805
83828
|
onPaste: e => {
|
|
83806
83829
|
const pickerEl = ref.current;
|
|
@@ -83826,6 +83849,10 @@ const PickerButton = props => {
|
|
|
83826
83849
|
dispatchRequestSetUIState(inputRef.current, pasteValue, {
|
|
83827
83850
|
event: e
|
|
83828
83851
|
});
|
|
83852
|
+
sendClosedPickerChange(inputRef.current, {
|
|
83853
|
+
event: e,
|
|
83854
|
+
name: "paste"
|
|
83855
|
+
});
|
|
83829
83856
|
}
|
|
83830
83857
|
});
|
|
83831
83858
|
e.preventDefault();
|
|
@@ -84056,7 +84083,8 @@ const requestPickerListEntry = (pickerEl, pickerInputEl, e, goal) => {
|
|
|
84056
84083
|
return;
|
|
84057
84084
|
}
|
|
84058
84085
|
const {
|
|
84059
|
-
id: entry
|
|
84086
|
+
id: entry,
|
|
84087
|
+
requester
|
|
84060
84088
|
} = e.detail;
|
|
84061
84089
|
const isThere = uiState.some(item => compareTwoJsValues(item, entry));
|
|
84062
84090
|
if (goal === "select" ? isThere : !isThere) {
|
|
@@ -84071,6 +84099,11 @@ const requestPickerListEntry = (pickerEl, pickerInputEl, e, goal) => {
|
|
|
84071
84099
|
dispatchRequestSetUIState(pickerInputEl, uiStateNext, {
|
|
84072
84100
|
event: e
|
|
84073
84101
|
});
|
|
84102
|
+
sendClosedPickerChange(pickerInputEl, {
|
|
84103
|
+
event: e,
|
|
84104
|
+
name: goal,
|
|
84105
|
+
requester
|
|
84106
|
+
});
|
|
84074
84107
|
}
|
|
84075
84108
|
});
|
|
84076
84109
|
};
|
|
@@ -84543,7 +84576,9 @@ const PickerFirstResolver = props => {
|
|
|
84543
84576
|
* the `<Picker type="confirm">` the cross then is, plain text or JSX. Asked
|
|
84544
84577
|
* BEFORE the ui state is emptied, so answering no leaves the field exactly
|
|
84545
84578
|
* as it was; answering yes clears and sends, and the picker's own action
|
|
84546
|
-
* receives the cleared value like any other choice.
|
|
84579
|
+
* receives the cleared value like any other choice. A cut (Ctrl+X) on such a
|
|
84580
|
+
* picker copies the value and leaves it in place: no clear without the
|
|
84581
|
+
* question.
|
|
84547
84582
|
* @param {"icon"|"headless"|"discrete"|"button"|"text"|"bare"|"picker"} [variant]
|
|
84548
84583
|
* How the trigger is drawn. `"button"` is a Button's drawing — surface,
|
|
84549
84584
|
* padding, a centered label, no chevron — and what a `type="confirm"` picker
|
|
@@ -96218,6 +96253,10 @@ const css$5 = /* css */`@keyframes navi_image_shimmer {
|
|
|
96218
96253
|
`;
|
|
96219
96254
|
const DEFAULT_PLACEHOLDER_LIGHT = "#d4d8dd";
|
|
96220
96255
|
const DEFAULT_PLACEHOLDER_DARK = "#374151";
|
|
96256
|
+
// On an <img>, width/height describe the image: they stay HTML attributes.
|
|
96257
|
+
// Box would write them as inline styles, which beat every stylesheet rule, so
|
|
96258
|
+
// an image given its file's size (3840×2160) would overflow the box sizing it.
|
|
96259
|
+
const IMAGE_ATTRIBUTE_PROP_SET = new Set(["width", "height"]);
|
|
96221
96260
|
|
|
96222
96261
|
/**
|
|
96223
96262
|
* The props may come in any order: `src` and `srcset` reach the element after
|
|
@@ -96226,6 +96265,12 @@ const DEFAULT_PLACEHOLDER_DARK = "#374151";
|
|
|
96226
96265
|
* decides whether to load when `src` is set, and an image whose `src` came
|
|
96227
96266
|
* first loads, lazy or not.
|
|
96228
96267
|
*
|
|
96268
|
+
* @param {number} [props.width] - The image's width in pixels, written as the
|
|
96269
|
+
* `<img>` attribute, like `height`. Given both, the browser reserves the
|
|
96270
|
+
* aspect ratio before the bytes arrive. They size the element only while no
|
|
96271
|
+
* stylesheet rule does: a box that holds the image sizes it with CSS (or
|
|
96272
|
+
* `style`) and keeps the ratio.
|
|
96273
|
+
* @param {number} [props.height] - The image's height in pixels, see `width`.
|
|
96229
96274
|
* @param {string|false} [props.placeholderColor] - Background color shown while the image loads.
|
|
96230
96275
|
* Defaults to a light gray. Pass `false` to disable.
|
|
96231
96276
|
* @param {boolean} [props.placeholderDark] - Use a dark default placeholder color,
|
|
@@ -96250,6 +96295,7 @@ const Image = ({
|
|
|
96250
96295
|
...rest,
|
|
96251
96296
|
as: "img",
|
|
96252
96297
|
baseClassName: "navi_image",
|
|
96298
|
+
childPropSet: IMAGE_ATTRIBUTE_PROP_SET,
|
|
96253
96299
|
"navi-placeholder": loadedRef.current ? undefined : "",
|
|
96254
96300
|
style: {
|
|
96255
96301
|
"--placeholder-color": resolvedPlaceholder || undefined,
|