@jsenv/navi 0.29.233 → 0.29.235
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 -38
- package/dist/dev/jsenv_navi.js.map +9 -9
- package/dist/jsenv_navi.js +111 -38
- package/dist/jsenv_navi.js.map +9 -9
- 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/dev/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
|
*
|
|
@@ -4182,22 +4197,16 @@ const createActionProxyFromSignal = (
|
|
|
4182
4197
|
dataSignal: proxySignal("dataSignal", "data"),
|
|
4183
4198
|
});
|
|
4184
4199
|
Object.preventExtensions(actionProxy);
|
|
4185
|
-
|
|
4186
|
-
//
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
// eslint-disable-next-line no-unused-expressions
|
|
4196
|
-
proxyParamsSignal.value;
|
|
4197
|
-
_updateTarget({
|
|
4198
|
-
changeCause: "params_signal_change",
|
|
4199
|
-
});
|
|
4200
|
-
});
|
|
4200
|
+
|
|
4201
|
+
// What the proxy shows of the action it stands for is wired BEFORE the
|
|
4202
|
+
// effects below, because those resolve the first target as they are created:
|
|
4203
|
+
// a subscriber registered after them misses that first change and reads the
|
|
4204
|
+
// placeholders assigned above until a SECOND one happens. A proxy answering
|
|
4205
|
+
// `params: undefined` is then taken for a proxy holding no params, and
|
|
4206
|
+
// whoever asks it to run on truthy params does nothing (actionRunEffect's
|
|
4207
|
+
// onChange). The caller's own callbacks (onChange, runOnce, rerunOnChange)
|
|
4208
|
+
// stay below on purpose: they are told about changes, not about the proxy
|
|
4209
|
+
// coming into existence.
|
|
4201
4210
|
onActionTargetChange((actionTarget) => {
|
|
4202
4211
|
const currentAction = actionTarget || action;
|
|
4203
4212
|
nameSignal.value = `[Proxy] ${currentAction.name}`;
|
|
@@ -4232,7 +4241,22 @@ const createActionProxyFromSignal = (
|
|
|
4232
4241
|
});
|
|
4233
4242
|
setActionPrivateProperties(actionProxy, proxyPrivateProperties);
|
|
4234
4243
|
}
|
|
4235
|
-
|
|
4244
|
+
// Watch for changes in the original paramsSignal and update ours
|
|
4245
|
+
// (original signal wins over any replaceParams calls)
|
|
4246
|
+
weakEffect(
|
|
4247
|
+
[paramsSignal, proxyParamsSignal],
|
|
4248
|
+
(paramsSignalRef, proxyParamsSignalRef) => {
|
|
4249
|
+
const newParams = paramsSignalRef.value;
|
|
4250
|
+
proxyParamsSignalRef.value = newParams;
|
|
4251
|
+
},
|
|
4252
|
+
);
|
|
4253
|
+
weakEffect([action], () => {
|
|
4254
|
+
// eslint-disable-next-line no-unused-expressions
|
|
4255
|
+
proxyParamsSignal.value;
|
|
4256
|
+
_updateTarget({
|
|
4257
|
+
changeCause: "params_signal_change",
|
|
4258
|
+
});
|
|
4259
|
+
});
|
|
4236
4260
|
actionProxy.replaceParams = (newParams) => {
|
|
4237
4261
|
if (currentAction === action) {
|
|
4238
4262
|
const currentParams = proxyParamsSignal.value;
|
|
@@ -22806,10 +22830,11 @@ let baseUrl;
|
|
|
22806
22830
|
* "/places/:placeId". The url "/admin/places/42" matches, and `buildUrl` puts
|
|
22807
22831
|
* the prefix back.
|
|
22808
22832
|
*
|
|
22809
|
-
* Call it in the routes module, above
|
|
22810
|
-
*
|
|
22811
|
-
*
|
|
22812
|
-
* address below it (see
|
|
22833
|
+
* Call it in the routes module, above `setupRoutes()`: the base url is read
|
|
22834
|
+
* whenever an address is matched or built, and the first match happens as the
|
|
22835
|
+
* routes are set up — an entry point calling it after importing them is too
|
|
22836
|
+
* late. The server must serve the document for every address below it (see
|
|
22837
|
+
* docs/navigation.md).
|
|
22813
22838
|
*
|
|
22814
22839
|
* @param {string} value - the document's own url, absolute or relative.
|
|
22815
22840
|
*/
|
|
@@ -48380,6 +48405,16 @@ const ButtonConfirm = ({
|
|
|
48380
48405
|
restoreFocusRef.current = restoreFocus;
|
|
48381
48406
|
setAsking(false);
|
|
48382
48407
|
};
|
|
48408
|
+
const {
|
|
48409
|
+
action,
|
|
48410
|
+
command,
|
|
48411
|
+
onActionEnd,
|
|
48412
|
+
onClick
|
|
48413
|
+
} = props;
|
|
48414
|
+
// A submit button does not run anything itself: the form around it does,
|
|
48415
|
+
// with the button as requester, and the outcome never comes back through the
|
|
48416
|
+
// button's own action events. The question then waits on the form.
|
|
48417
|
+
const waitsForForm = !action && command === "--navi-send";
|
|
48383
48418
|
useLayoutEffect(() => {
|
|
48384
48419
|
if (asking) {
|
|
48385
48420
|
// The press was aimed at this button, and the button that answers now is
|
|
@@ -48392,7 +48427,39 @@ const ButtonConfirm = ({
|
|
|
48392
48427
|
if (found) {
|
|
48393
48428
|
moveFocusTo(found.target);
|
|
48394
48429
|
}
|
|
48395
|
-
|
|
48430
|
+
if (!waitsForForm) {
|
|
48431
|
+
return undefined;
|
|
48432
|
+
}
|
|
48433
|
+
const buttonEl = props.ref.current;
|
|
48434
|
+
const formEl = buttonEl.form || buttonEl.closest("form");
|
|
48435
|
+
if (!formEl) {
|
|
48436
|
+
return undefined;
|
|
48437
|
+
}
|
|
48438
|
+
// Same reading as watchActionCompletion (control_action.js): a send that
|
|
48439
|
+
// failed leaves its message on the button that asked, so the question
|
|
48440
|
+
// stays up with it; one that went through is answered.
|
|
48441
|
+
const onFormActionStart = actionStartEvent => {
|
|
48442
|
+
const {
|
|
48443
|
+
requester,
|
|
48444
|
+
addSideEffect
|
|
48445
|
+
} = actionStartEvent.detail;
|
|
48446
|
+
if (requester && requester !== buttonEl) {
|
|
48447
|
+
return;
|
|
48448
|
+
}
|
|
48449
|
+
addSideEffect(({
|
|
48450
|
+
error,
|
|
48451
|
+
aborted
|
|
48452
|
+
}) => {
|
|
48453
|
+
if (error || aborted) {
|
|
48454
|
+
return;
|
|
48455
|
+
}
|
|
48456
|
+
cancel(true);
|
|
48457
|
+
});
|
|
48458
|
+
};
|
|
48459
|
+
formEl.addEventListener("navi_action_start", onFormActionStart);
|
|
48460
|
+
return () => {
|
|
48461
|
+
formEl.removeEventListener("navi_action_start", onFormActionStart);
|
|
48462
|
+
};
|
|
48396
48463
|
}
|
|
48397
48464
|
if (restoreFocusRef.current) {
|
|
48398
48465
|
restoreFocusRef.current = false;
|
|
@@ -48404,28 +48471,22 @@ const ButtonConfirm = ({
|
|
|
48404
48471
|
moveFocusTo(buttonEl);
|
|
48405
48472
|
}
|
|
48406
48473
|
}
|
|
48474
|
+
return undefined;
|
|
48407
48475
|
}, [asking]);
|
|
48408
48476
|
if (!asking) {
|
|
48409
48477
|
return jsx(Next, {
|
|
48410
48478
|
...props,
|
|
48411
|
-
// The first press is the question, not the act
|
|
48412
|
-
//
|
|
48413
|
-
//
|
|
48414
|
-
type: "button",
|
|
48479
|
+
// The first press is the question, not the act: nothing that acts
|
|
48480
|
+
// reaches the ui. What was decided about the button itself (readOnly,
|
|
48481
|
+
// cta, the label) stays.
|
|
48415
48482
|
action: undefined,
|
|
48416
48483
|
command: undefined,
|
|
48417
48484
|
href: undefined,
|
|
48418
|
-
route: undefined,
|
|
48419
48485
|
onClick: () => {
|
|
48420
48486
|
setAsking(true);
|
|
48421
48487
|
}
|
|
48422
48488
|
});
|
|
48423
48489
|
}
|
|
48424
|
-
const {
|
|
48425
|
-
action,
|
|
48426
|
-
onActionEnd,
|
|
48427
|
-
onClick
|
|
48428
|
-
} = props;
|
|
48429
48490
|
return jsxs("span", {
|
|
48430
48491
|
className: "navi_button_confirm",
|
|
48431
48492
|
ref: askingRef,
|
|
@@ -48451,7 +48512,7 @@ const ButtonConfirm = ({
|
|
|
48451
48512
|
"data-testid": confirmTestId || props["data-testid"],
|
|
48452
48513
|
onClick: e => {
|
|
48453
48514
|
onClick?.(e);
|
|
48454
|
-
if (!action) {
|
|
48515
|
+
if (!action && !waitsForForm) {
|
|
48455
48516
|
// A command runs on the press itself: there is nothing to wait for,
|
|
48456
48517
|
// the question is answered as soon as it is pressed.
|
|
48457
48518
|
cancel(true);
|
|
@@ -48686,7 +48747,10 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48686
48747
|
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
48687
48748
|
* does what the button was for. `true` asks navi's default question. The
|
|
48688
48749
|
* press stays under the finger and there is nothing to dismiss — "Annuler",
|
|
48689
|
-
* Escape, or the focus leaving puts the button back.
|
|
48750
|
+
* Escape, or the focus leaving puts the button back. So does the act itself,
|
|
48751
|
+
* once it went through: an `action` that succeeded, a submit whose form has
|
|
48752
|
+
* sent, a command on the press that runs it — while a failure keeps the
|
|
48753
|
+
* question up, its message on the button that asked. Use `<Picker
|
|
48690
48754
|
* type="confirm">` instead when the question is long enough to want a popup,
|
|
48691
48755
|
* or when the row has no space for it.
|
|
48692
48756
|
* @param {import("ignore:preact").ComponentChildren} [confirmLabel] Label of the
|
|
@@ -48709,7 +48773,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48709
48773
|
* (it wears it itself) and loses the shrink under the finger, which has
|
|
48710
48774
|
* nothing left to scale but the interactive area itself.
|
|
48711
48775
|
*/
|
|
48712
|
-
const Button = createComponentResolver([ButtonFirstResolver,
|
|
48776
|
+
const Button = createComponentResolver([ButtonFirstResolver, ButtonRouteResolver, ButtonCommandPropResolver, ButtonConfirmResolver, ButtonUI]);
|
|
48713
48777
|
|
|
48714
48778
|
installImportMetaCssBuild(import.meta);
|
|
48715
48779
|
const css$O = /* css */`.navi_control_swap {
|
|
@@ -82452,6 +82516,7 @@ const css$9 = /* css */`@layer navi;
|
|
|
82452
82516
|
* unitSize?: string,
|
|
82453
82517
|
* unitSizeRatio?: number,
|
|
82454
82518
|
* unitColor?: string,
|
|
82519
|
+
* unitSpacing?: string | number,
|
|
82455
82520
|
* label?: string,
|
|
82456
82521
|
* size?: string,
|
|
82457
82522
|
* lang?: string,
|
|
@@ -82462,6 +82527,13 @@ const css$9 = /* css */`@layer navi;
|
|
|
82462
82527
|
* bold?: boolean,
|
|
82463
82528
|
* [key: string]: any,
|
|
82464
82529
|
* }>}
|
|
82530
|
+
*
|
|
82531
|
+
* @param {string|number} [unitSpacing]
|
|
82532
|
+
* Gap between the value and the unit when `unitPosition="right"`. Accepts a
|
|
82533
|
+
* size token (`"s"`, `"m"`, …), a CSS length string, a number (px) or
|
|
82534
|
+
* `"0"` to glue the unit to the value. Defaults to a regular space
|
|
82535
|
+
* character. Ignored when `unitPosition="bottom"`, where the unit sits on
|
|
82536
|
+
* its own line.
|
|
82465
82537
|
*/
|
|
82466
82538
|
const Quantity = ({
|
|
82467
82539
|
children,
|
|
@@ -82470,6 +82542,7 @@ const Quantity = ({
|
|
|
82470
82542
|
unitSize = "smaller",
|
|
82471
82543
|
unitSizeRatio,
|
|
82472
82544
|
unitColor,
|
|
82545
|
+
unitSpacing,
|
|
82473
82546
|
label,
|
|
82474
82547
|
size,
|
|
82475
82548
|
lang,
|
|
@@ -82508,7 +82581,7 @@ const Quantity = ({
|
|
|
82508
82581
|
}), jsxs(Text, {
|
|
82509
82582
|
className: "navi_quantity_body",
|
|
82510
82583
|
size: size,
|
|
82511
|
-
spacing: unitBottom ? jsx("br", {}) :
|
|
82584
|
+
spacing: unitBottom ? jsx("br", {}) : unitSpacing,
|
|
82512
82585
|
children: [jsx("span", {
|
|
82513
82586
|
className: "navi_quantity_value",
|
|
82514
82587
|
children: loading ? jsx(Icon, {
|