@jsenv/navi 0.29.233 → 0.29.234
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 +101 -37
- package/dist/dev/jsenv_navi.js.map +8 -8
- package/dist/jsenv_navi.js +101 -37
- package/dist/jsenv_navi.js.map +8 -8
- package/docs/form_changed.md +4 -2
- package/docs/i18n.md +2 -2
- package/docs/navigation.md +29 -9
- package/docs/resource.md +4 -3
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -1952,6 +1952,8 @@ const stringifyForDisplay = (
|
|
|
1952
1952
|
return String(value);
|
|
1953
1953
|
};
|
|
1954
1954
|
|
|
1955
|
+
const SYMBOL_OBJECT_SIGNAL = Symbol.for("navi_object_signal");
|
|
1956
|
+
|
|
1955
1957
|
/*
|
|
1956
1958
|
* Deep structural equality for arbitrary JS values — what `===` can't do but this
|
|
1957
1959
|
* codebase constantly needs: memoization cache keys ({ id: 1 } equal to { id: 1 }),
|
|
@@ -2060,6 +2062,21 @@ const compareTwoJsValues = (
|
|
|
2060
2062
|
return result;
|
|
2061
2063
|
};
|
|
2062
2064
|
const compareComposite = (a, b) => {
|
|
2065
|
+
// An object carrying its signal (a resource relation read through
|
|
2066
|
+
// `.one()` or `.many()`) is that signal in the costume of its current
|
|
2067
|
+
// value: it says "follow this", not "this". Two of them are the same
|
|
2068
|
+
// thing when they follow the same signal, whatever they show right now —
|
|
2069
|
+
// and neither is ever a plain value that happens to look alike. Without
|
|
2070
|
+
// this a params object bound to the relation and one holding the row as
|
|
2071
|
+
// data would pass for equal, and the binding would be handed back for the
|
|
2072
|
+
// row (see bindParams' child cache in actions.js). Decided ahead of
|
|
2073
|
+
// SYMBOL_IDENTITY: the costume is a copy of the row and carries its
|
|
2074
|
+
// identity, which is exactly the sameness that must not count here.
|
|
2075
|
+
const aObjectSignal = a[SYMBOL_OBJECT_SIGNAL];
|
|
2076
|
+
const bObjectSignal = b[SYMBOL_OBJECT_SIGNAL];
|
|
2077
|
+
if (aObjectSignal || bObjectSignal) {
|
|
2078
|
+
return aObjectSignal === bObjectSignal;
|
|
2079
|
+
}
|
|
2063
2080
|
const aIsArray = Array.isArray(a);
|
|
2064
2081
|
const bIsArray = Array.isArray(b);
|
|
2065
2082
|
if (aIsArray !== bIsArray) {
|
|
@@ -2540,8 +2557,6 @@ const setActionPrivateProperties = (action, properties) => {
|
|
|
2540
2557
|
actionPrivatePropertiesWeakMap.set(action, properties);
|
|
2541
2558
|
};
|
|
2542
2559
|
|
|
2543
|
-
const SYMBOL_OBJECT_SIGNAL = Symbol.for("navi_object_signal");
|
|
2544
|
-
|
|
2545
2560
|
/*
|
|
2546
2561
|
* Actions: async callbacks wrapped in reactive state.
|
|
2547
2562
|
*
|
|
@@ -4122,22 +4137,16 @@ const createActionProxyFromSignal = (
|
|
|
4122
4137
|
dataSignal: proxySignal("dataSignal", "data"),
|
|
4123
4138
|
});
|
|
4124
4139
|
Object.preventExtensions(actionProxy);
|
|
4125
|
-
|
|
4126
|
-
//
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
// eslint-disable-next-line no-unused-expressions
|
|
4136
|
-
proxyParamsSignal.value;
|
|
4137
|
-
_updateTarget({
|
|
4138
|
-
changeCause: "params_signal_change",
|
|
4139
|
-
});
|
|
4140
|
-
});
|
|
4140
|
+
|
|
4141
|
+
// What the proxy shows of the action it stands for is wired BEFORE the
|
|
4142
|
+
// effects below, because those resolve the first target as they are created:
|
|
4143
|
+
// a subscriber registered after them misses that first change and reads the
|
|
4144
|
+
// placeholders assigned above until a SECOND one happens. A proxy answering
|
|
4145
|
+
// `params: undefined` is then taken for a proxy holding no params, and
|
|
4146
|
+
// whoever asks it to run on truthy params does nothing (actionRunEffect's
|
|
4147
|
+
// onChange). The caller's own callbacks (onChange, runOnce, rerunOnChange)
|
|
4148
|
+
// stay below on purpose: they are told about changes, not about the proxy
|
|
4149
|
+
// coming into existence.
|
|
4141
4150
|
onActionTargetChange((actionTarget) => {
|
|
4142
4151
|
const currentAction = actionTarget || action;
|
|
4143
4152
|
nameSignal.value = `[Proxy] ${currentAction.name}`;
|
|
@@ -4172,7 +4181,22 @@ const createActionProxyFromSignal = (
|
|
|
4172
4181
|
});
|
|
4173
4182
|
setActionPrivateProperties(actionProxy, proxyPrivateProperties);
|
|
4174
4183
|
}
|
|
4175
|
-
|
|
4184
|
+
// Watch for changes in the original paramsSignal and update ours
|
|
4185
|
+
// (original signal wins over any replaceParams calls)
|
|
4186
|
+
weakEffect(
|
|
4187
|
+
[paramsSignal, proxyParamsSignal],
|
|
4188
|
+
(paramsSignalRef, proxyParamsSignalRef) => {
|
|
4189
|
+
const newParams = paramsSignalRef.value;
|
|
4190
|
+
proxyParamsSignalRef.value = newParams;
|
|
4191
|
+
},
|
|
4192
|
+
);
|
|
4193
|
+
weakEffect([action], () => {
|
|
4194
|
+
// eslint-disable-next-line no-unused-expressions
|
|
4195
|
+
proxyParamsSignal.value;
|
|
4196
|
+
_updateTarget({
|
|
4197
|
+
changeCause: "params_signal_change",
|
|
4198
|
+
});
|
|
4199
|
+
});
|
|
4176
4200
|
actionProxy.replaceParams = (newParams) => {
|
|
4177
4201
|
if (currentAction === action) {
|
|
4178
4202
|
const currentParams = proxyParamsSignal.value;
|
|
@@ -22474,10 +22498,11 @@ let baseUrl;
|
|
|
22474
22498
|
* "/places/:placeId". The url "/admin/places/42" matches, and `buildUrl` puts
|
|
22475
22499
|
* the prefix back.
|
|
22476
22500
|
*
|
|
22477
|
-
* Call it in the routes module, above
|
|
22478
|
-
*
|
|
22479
|
-
*
|
|
22480
|
-
* address below it (see
|
|
22501
|
+
* Call it in the routes module, above `setupRoutes()`: the base url is read
|
|
22502
|
+
* whenever an address is matched or built, and the first match happens as the
|
|
22503
|
+
* routes are set up — an entry point calling it after importing them is too
|
|
22504
|
+
* late. The server must serve the document for every address below it (see
|
|
22505
|
+
* docs/navigation.md).
|
|
22481
22506
|
*
|
|
22482
22507
|
* @param {string} value - the document's own url, absolute or relative.
|
|
22483
22508
|
*/
|
|
@@ -47729,6 +47754,16 @@ const ButtonConfirm = ({
|
|
|
47729
47754
|
restoreFocusRef.current = restoreFocus;
|
|
47730
47755
|
setAsking(false);
|
|
47731
47756
|
};
|
|
47757
|
+
const {
|
|
47758
|
+
action,
|
|
47759
|
+
command,
|
|
47760
|
+
onActionEnd,
|
|
47761
|
+
onClick
|
|
47762
|
+
} = props;
|
|
47763
|
+
// A submit button does not run anything itself: the form around it does,
|
|
47764
|
+
// with the button as requester, and the outcome never comes back through the
|
|
47765
|
+
// button's own action events. The question then waits on the form.
|
|
47766
|
+
const waitsForForm = !action && command === "--navi-send";
|
|
47732
47767
|
useLayoutEffect(() => {
|
|
47733
47768
|
if (asking) {
|
|
47734
47769
|
// The press was aimed at this button, and the button that answers now is
|
|
@@ -47741,7 +47776,39 @@ const ButtonConfirm = ({
|
|
|
47741
47776
|
if (found) {
|
|
47742
47777
|
moveFocusTo(found.target);
|
|
47743
47778
|
}
|
|
47744
|
-
|
|
47779
|
+
if (!waitsForForm) {
|
|
47780
|
+
return undefined;
|
|
47781
|
+
}
|
|
47782
|
+
const buttonEl = props.ref.current;
|
|
47783
|
+
const formEl = buttonEl.form || buttonEl.closest("form");
|
|
47784
|
+
if (!formEl) {
|
|
47785
|
+
return undefined;
|
|
47786
|
+
}
|
|
47787
|
+
// Same reading as watchActionCompletion (control_action.js): a send that
|
|
47788
|
+
// failed leaves its message on the button that asked, so the question
|
|
47789
|
+
// stays up with it; one that went through is answered.
|
|
47790
|
+
const onFormActionStart = actionStartEvent => {
|
|
47791
|
+
const {
|
|
47792
|
+
requester,
|
|
47793
|
+
addSideEffect
|
|
47794
|
+
} = actionStartEvent.detail;
|
|
47795
|
+
if (requester && requester !== buttonEl) {
|
|
47796
|
+
return;
|
|
47797
|
+
}
|
|
47798
|
+
addSideEffect(({
|
|
47799
|
+
error,
|
|
47800
|
+
aborted
|
|
47801
|
+
}) => {
|
|
47802
|
+
if (error || aborted) {
|
|
47803
|
+
return;
|
|
47804
|
+
}
|
|
47805
|
+
cancel(true);
|
|
47806
|
+
});
|
|
47807
|
+
};
|
|
47808
|
+
formEl.addEventListener("navi_action_start", onFormActionStart);
|
|
47809
|
+
return () => {
|
|
47810
|
+
formEl.removeEventListener("navi_action_start", onFormActionStart);
|
|
47811
|
+
};
|
|
47745
47812
|
}
|
|
47746
47813
|
if (restoreFocusRef.current) {
|
|
47747
47814
|
restoreFocusRef.current = false;
|
|
@@ -47753,28 +47820,22 @@ const ButtonConfirm = ({
|
|
|
47753
47820
|
moveFocusTo(buttonEl);
|
|
47754
47821
|
}
|
|
47755
47822
|
}
|
|
47823
|
+
return undefined;
|
|
47756
47824
|
}, [asking]);
|
|
47757
47825
|
if (!asking) {
|
|
47758
47826
|
return jsx(Next, {
|
|
47759
47827
|
...props,
|
|
47760
|
-
// The first press is the question, not the act
|
|
47761
|
-
//
|
|
47762
|
-
//
|
|
47763
|
-
type: "button",
|
|
47828
|
+
// The first press is the question, not the act: nothing that acts
|
|
47829
|
+
// reaches the ui. What was decided about the button itself (readOnly,
|
|
47830
|
+
// cta, the label) stays.
|
|
47764
47831
|
action: undefined,
|
|
47765
47832
|
command: undefined,
|
|
47766
47833
|
href: undefined,
|
|
47767
|
-
route: undefined,
|
|
47768
47834
|
onClick: () => {
|
|
47769
47835
|
setAsking(true);
|
|
47770
47836
|
}
|
|
47771
47837
|
});
|
|
47772
47838
|
}
|
|
47773
|
-
const {
|
|
47774
|
-
action,
|
|
47775
|
-
onActionEnd,
|
|
47776
|
-
onClick
|
|
47777
|
-
} = props;
|
|
47778
47839
|
return jsxs("span", {
|
|
47779
47840
|
className: "navi_button_confirm",
|
|
47780
47841
|
ref: askingRef,
|
|
@@ -47800,7 +47861,7 @@ const ButtonConfirm = ({
|
|
|
47800
47861
|
"data-testid": confirmTestId || props["data-testid"],
|
|
47801
47862
|
onClick: e => {
|
|
47802
47863
|
onClick?.(e);
|
|
47803
|
-
if (!action) {
|
|
47864
|
+
if (!action && !waitsForForm) {
|
|
47804
47865
|
// A command runs on the press itself: there is nothing to wait for,
|
|
47805
47866
|
// the question is answered as soon as it is pressed.
|
|
47806
47867
|
cancel(true);
|
|
@@ -48032,7 +48093,10 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48032
48093
|
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
48033
48094
|
* does what the button was for. `true` asks navi's default question. The
|
|
48034
48095
|
* press stays under the finger and there is nothing to dismiss — "Annuler",
|
|
48035
|
-
* Escape, or the focus leaving puts the button back.
|
|
48096
|
+
* Escape, or the focus leaving puts the button back. So does the act itself,
|
|
48097
|
+
* once it went through: an `action` that succeeded, a submit whose form has
|
|
48098
|
+
* sent, a command on the press that runs it — while a failure keeps the
|
|
48099
|
+
* question up, its message on the button that asked. Use `<Picker
|
|
48036
48100
|
* type="confirm">` instead when the question is long enough to want a popup,
|
|
48037
48101
|
* or when the row has no space for it.
|
|
48038
48102
|
* @param {import("ignore:preact").ComponentChildren} [confirmLabel] Label of the
|
|
@@ -48055,7 +48119,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48055
48119
|
* (it wears it itself) and loses the shrink under the finger, which has
|
|
48056
48120
|
* nothing left to scale but the interactive area itself.
|
|
48057
48121
|
*/
|
|
48058
|
-
const Button = createComponentResolver([ButtonFirstResolver,
|
|
48122
|
+
const Button = createComponentResolver([ButtonFirstResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonConfirmResolver, ButtonUI]);
|
|
48059
48123
|
|
|
48060
48124
|
installImportMetaCssBuild(import.meta);
|
|
48061
48125
|
const css$O = /* css */`.navi_control_swap {
|