@jsenv/navi 0.29.435 → 0.29.436
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 +195 -133
- package/dist/dev/jsenv_navi.js.map +11 -10
- package/dist/jsenv_navi.js +195 -133
- package/dist/jsenv_navi.js.map +11 -10
- package/docs/route_transitions.md +7 -0
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -7,11 +7,11 @@ import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchIntern
|
|
|
7
7
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
8
8
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
9
9
|
import { options, isValidElement, createContext, render as render$1, h, toChildArray, createRef, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
10
|
-
import { useErrorBoundary, useLayoutEffect, useContext, useState, useMemo, useRef, useEffect,
|
|
10
|
+
import { useErrorBoundary, useLayoutEffect, useContext, useState, useMemo, useRef, useEffect, useReducer, useCallback, useId } from "preact/hooks";
|
|
11
11
|
import { humanizeI18n, prefixFirstAndIndentRemainingLines, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatTime, formatMinuteDuration, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
|
|
12
12
|
export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
|
|
13
13
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
14
|
-
import { CHAR_CLASS_PRESETS, createValidity,
|
|
14
|
+
import { CHAR_CLASS_PRESETS, createValidity, durationContainsNaN, compareTwoDurations, TYPE_RULE, durationToSeconds, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, parseDuration, durationToISOString } from "@jsenv/validity";
|
|
15
15
|
export { compareTwoDurations, durationContainsNaN, durationToHours, durationToISOString, durationToMinutes, durationToNumber, durationToSeconds, durationToString, parseDuration } from "@jsenv/validity";
|
|
16
16
|
import { Suspense, createPortal, forwardRef } from "preact/compat";
|
|
17
17
|
|
|
@@ -971,8 +971,10 @@ naviI18n.addAll({
|
|
|
971
971
|
},
|
|
972
972
|
});
|
|
973
973
|
|
|
974
|
-
// Spin messages — the ends of what one steps through
|
|
975
|
-
//
|
|
974
|
+
// Spin messages — the ends of what one steps through when the control holding
|
|
975
|
+
// it has no sentence of its own for a bound (a day or a number says its
|
|
976
|
+
// constraint.min/max one, see picker_spin.jsx): said without naming what it is
|
|
977
|
+
// made of, so they fit pages, sizes or any list of items.
|
|
976
978
|
naviI18n.addAll({
|
|
977
979
|
"spin.previous": {
|
|
978
980
|
en: "Previous",
|
|
@@ -11207,6 +11209,42 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
11207
11209
|
return `${urlObject.origin}${urlObject.pathname}`;
|
|
11208
11210
|
};
|
|
11209
11211
|
|
|
11212
|
+
/**
|
|
11213
|
+
* What a component can learn about the page it is rendered in, from the route
|
|
11214
|
+
* container rendering that page (see useContentKeepingLeavingPages in
|
|
11215
|
+
* route.jsx). `null` outside of any route container.
|
|
11216
|
+
*
|
|
11217
|
+
* - `isShown()` — whether the container would still show the page. False as
|
|
11218
|
+
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
11219
|
+
* hidden until the route transition leaving it is over, and nothing it reads
|
|
11220
|
+
* is worth a render any more. False for good once it is kept while leaving: a
|
|
11221
|
+
* return to its route mounts the page anew. Asked outside any render — by a
|
|
11222
|
+
* subscription deciding whether to re-render — and it reads the routes
|
|
11223
|
+
* untracked, so asking subscribes nobody to them.
|
|
11224
|
+
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
11225
|
+
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
11226
|
+
* bar) steps out rather than showing with the page arriving.
|
|
11227
|
+
*/
|
|
11228
|
+
const RoutePageContext = createContext(null);
|
|
11229
|
+
|
|
11230
|
+
/**
|
|
11231
|
+
* A container has put its page on screen — or as much of it as it can.
|
|
11232
|
+
*
|
|
11233
|
+
* A route matching is a signal changing, and the page it selects reaches the
|
|
11234
|
+
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
11235
|
+
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
11236
|
+
* as it has been decided (a travel about to have its picture taken by the
|
|
11237
|
+
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
11238
|
+
*
|
|
11239
|
+
* A page waiting on data is announced too, by the boundary showing its loading
|
|
11240
|
+
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
11241
|
+
* screen is what the browser is about to take a picture of, and a page that
|
|
11242
|
+
* cannot render yet would otherwise be waited on until the transition dies of
|
|
11243
|
+
* it. It lives in a module of its own for that: the async layer says it as much
|
|
11244
|
+
* as the router does, and neither can import the other.
|
|
11245
|
+
*/
|
|
11246
|
+
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
11247
|
+
|
|
11210
11248
|
const documentStateSignal = signal(null);
|
|
11211
11249
|
const useDocumentState = () => {
|
|
11212
11250
|
return documentStateSignal.value;
|
|
@@ -11466,24 +11504,6 @@ const readTopInWindow = element => {
|
|
|
11466
11504
|
return element.getBoundingClientRect().top;
|
|
11467
11505
|
};
|
|
11468
11506
|
|
|
11469
|
-
/**
|
|
11470
|
-
* A container has put its page on screen — or as much of it as it can.
|
|
11471
|
-
*
|
|
11472
|
-
* A route matching is a signal changing, and the page it selects reaches the
|
|
11473
|
-
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
11474
|
-
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
11475
|
-
* as it has been decided (a travel about to have its picture taken by the
|
|
11476
|
-
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
11477
|
-
*
|
|
11478
|
-
* A page waiting on data is announced too, by the boundary showing its loading
|
|
11479
|
-
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
11480
|
-
* screen is what the browser is about to take a picture of, and a page that
|
|
11481
|
-
* cannot render yet would otherwise be waited on until the transition dies of
|
|
11482
|
-
* it. It lives in a module of its own for that: the async layer says it as much
|
|
11483
|
-
* as the router does, and neither can import the other.
|
|
11484
|
-
*/
|
|
11485
|
-
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
11486
|
-
|
|
11487
11507
|
/**
|
|
11488
11508
|
* Where a page was left, given back when one comes back to it.
|
|
11489
11509
|
*
|
|
@@ -13011,6 +13031,7 @@ const warnAboutOrphanGeneratedKeys = () => {
|
|
|
13011
13031
|
|
|
13012
13032
|
const NO_OP = () => {};
|
|
13013
13033
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
13034
|
+
const countRenders$1 = (count) => count + 1;
|
|
13014
13035
|
// What the computed below answers for a key the document state does not hold:
|
|
13015
13036
|
// key presence is the flag, and the value under a present key may be anything,
|
|
13016
13037
|
// undefined included.
|
|
@@ -13039,7 +13060,36 @@ const useNavStateBasic = (
|
|
|
13039
13060
|
[id],
|
|
13040
13061
|
);
|
|
13041
13062
|
const keyValue = keyValueComputed.value;
|
|
13042
|
-
|
|
13063
|
+
// A page the address has left keeps reading the entry it was shown on until
|
|
13064
|
+
// it is taken down (kept hidden while a route transition plays): the new
|
|
13065
|
+
// entry would close what was open there — a Picker as a cancel — where the
|
|
13066
|
+
// same page taken down at once is only unmounted. Its render still happens:
|
|
13067
|
+
// the state is written before the routes say the page is left.
|
|
13068
|
+
const routePage = useContext(RoutePageContext);
|
|
13069
|
+
const keyValueReadRef = useRef(keyValue);
|
|
13070
|
+
const keyValueWithheld =
|
|
13071
|
+
keyValue !== keyValueReadRef.current &&
|
|
13072
|
+
routePage !== null &&
|
|
13073
|
+
!routePage.isShown();
|
|
13074
|
+
if (!keyValueWithheld) {
|
|
13075
|
+
keyValueReadRef.current = keyValue;
|
|
13076
|
+
}
|
|
13077
|
+
const keyValueRead = keyValueReadRef.current;
|
|
13078
|
+
// Owed rather than dropped, as in useAsyncData: the address can come back
|
|
13079
|
+
// before the container renders (a redirect straight back), and the page then
|
|
13080
|
+
// stays on screen.
|
|
13081
|
+
const [, rerender] = useReducer(countRenders$1, 0);
|
|
13082
|
+
useEffect(() => {
|
|
13083
|
+
if (!keyValueWithheld) {
|
|
13084
|
+
return undefined;
|
|
13085
|
+
}
|
|
13086
|
+
return observeRouteRender(() => {
|
|
13087
|
+
if (routePage.isShown()) {
|
|
13088
|
+
rerender();
|
|
13089
|
+
}
|
|
13090
|
+
});
|
|
13091
|
+
}, [keyValueWithheld]);
|
|
13092
|
+
const keyInState = keyValueRead !== KEY_NOT_IN_STATE;
|
|
13043
13093
|
const onLeaveRef = useRef(onLeave);
|
|
13044
13094
|
onLeaveRef.current = onLeave;
|
|
13045
13095
|
const prevKeyInStateRef = useRef(keyInState);
|
|
@@ -13074,7 +13124,7 @@ const useNavStateBasic = (
|
|
|
13074
13124
|
effectiveType = "replace";
|
|
13075
13125
|
}
|
|
13076
13126
|
|
|
13077
|
-
const currentValue = keyInState ?
|
|
13127
|
+
const currentValue = keyInState ? keyValueRead : defaultValue;
|
|
13078
13128
|
|
|
13079
13129
|
if (debug) {
|
|
13080
13130
|
console.debug(`useNavState(${id}) current value is ${currentValue}`);
|
|
@@ -13142,6 +13192,8 @@ const useNavStateBasic = (
|
|
|
13142
13192
|
/**
|
|
13143
13193
|
* Stores a named value in the browser's document state and returns it reactively.
|
|
13144
13194
|
* The component re-renders whenever the value changes (navigation, back/forward button).
|
|
13195
|
+
* In a page the address has left — kept hidden while a route transition plays —
|
|
13196
|
+
* the value stays the one of the entry that page was shown on.
|
|
13145
13197
|
*
|
|
13146
13198
|
* @param {string} id
|
|
13147
13199
|
* Unique key used to store the value in document state. Must be stable across renders.
|
|
@@ -20293,18 +20345,17 @@ const MIN_CONSTRAINT = {
|
|
|
20293
20345
|
if (cmp === null || cmp >= 0) {
|
|
20294
20346
|
return null;
|
|
20295
20347
|
}
|
|
20296
|
-
|
|
20297
|
-
return naviI18n("constraint.min.duration.default", {
|
|
20298
|
-
min: formatDuration(min),
|
|
20299
|
-
});
|
|
20348
|
+
return boundMessage(field, "min");
|
|
20300
20349
|
}
|
|
20301
20350
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
20302
20351
|
return null;
|
|
20303
20352
|
}
|
|
20304
|
-
const
|
|
20305
|
-
|
|
20353
|
+
const min = field.controlHostProps.min;
|
|
20354
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
20355
|
+
if (min === undefined || min === null || min === "") {
|
|
20306
20356
|
return null;
|
|
20307
20357
|
}
|
|
20358
|
+
const minString = String(min);
|
|
20308
20359
|
const type = field.controlHostProps.type;
|
|
20309
20360
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20310
20361
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -20320,40 +20371,16 @@ const MIN_CONSTRAINT = {
|
|
|
20320
20371
|
if (isNaN(numericValue)) {
|
|
20321
20372
|
return null;
|
|
20322
20373
|
}
|
|
20323
|
-
if (numericValue
|
|
20324
|
-
|
|
20325
|
-
return naviI18n(`constraint.min.hour.default`, {
|
|
20326
|
-
min: minString,
|
|
20327
|
-
});
|
|
20328
|
-
}
|
|
20329
|
-
if (naviInputType === "minute") {
|
|
20330
|
-
return naviI18n(`constraint.min.minute.default`, {
|
|
20331
|
-
min: minString,
|
|
20332
|
-
});
|
|
20333
|
-
}
|
|
20334
|
-
if (naviInputType === "second") {
|
|
20335
|
-
return naviI18n(`constraint.min.second.default`, {
|
|
20336
|
-
min: minString,
|
|
20337
|
-
});
|
|
20338
|
-
}
|
|
20339
|
-
if (naviInputType === "percentage") {
|
|
20340
|
-
return naviI18n(`constraint.min.percentage.default`, {
|
|
20341
|
-
min: minString,
|
|
20342
|
-
});
|
|
20343
|
-
}
|
|
20344
|
-
return naviI18n(`constraint.min.number.default`, {
|
|
20345
|
-
min: minString,
|
|
20346
|
-
});
|
|
20374
|
+
if (numericValue >= minNumber) {
|
|
20375
|
+
return null;
|
|
20347
20376
|
}
|
|
20348
|
-
return
|
|
20377
|
+
return boundMessage(field, "min");
|
|
20349
20378
|
}
|
|
20350
20379
|
if (type === "time") {
|
|
20351
20380
|
const [minHours, minMinutes] = minString.split(":").map(Number);
|
|
20352
20381
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
20353
20382
|
if (hours < minHours || (hours === minHours && minutes < minMinutes)) {
|
|
20354
|
-
return
|
|
20355
|
-
min: minString,
|
|
20356
|
-
});
|
|
20383
|
+
return boundMessage(field, "min");
|
|
20357
20384
|
}
|
|
20358
20385
|
return null;
|
|
20359
20386
|
}
|
|
@@ -20361,18 +20388,13 @@ const MIN_CONSTRAINT = {
|
|
|
20361
20388
|
// so they never need a min/max validation message.
|
|
20362
20389
|
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
20363
20390
|
if (valueAsString < minString) {
|
|
20364
|
-
|
|
20365
|
-
if (minString === todayIso) {
|
|
20366
|
-
return naviI18n("constraint.min.date.today.default");
|
|
20367
|
-
}
|
|
20368
|
-
return naviI18n("constraint.min.date.default", {
|
|
20369
|
-
min: formatDateIso(minString, type),
|
|
20370
|
-
});
|
|
20391
|
+
return boundMessage(field, "min");
|
|
20371
20392
|
}
|
|
20372
20393
|
return null;
|
|
20373
20394
|
}
|
|
20374
20395
|
return null;
|
|
20375
20396
|
},
|
|
20397
|
+
messageAtBound: (field) => boundMessage(field, "min"),
|
|
20376
20398
|
};
|
|
20377
20399
|
CONSTRAINT_ATTRIBUTE_SET.add("min");
|
|
20378
20400
|
|
|
@@ -20392,18 +20414,17 @@ const MAX_CONSTRAINT = {
|
|
|
20392
20414
|
if (cmp === null || cmp <= 0) {
|
|
20393
20415
|
return null;
|
|
20394
20416
|
}
|
|
20395
|
-
|
|
20396
|
-
return naviI18n("constraint.max.duration.default", {
|
|
20397
|
-
max: formatDuration(max),
|
|
20398
|
-
});
|
|
20417
|
+
return boundMessage(field, "max");
|
|
20399
20418
|
}
|
|
20400
20419
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
20401
20420
|
return null;
|
|
20402
20421
|
}
|
|
20403
|
-
const
|
|
20404
|
-
|
|
20422
|
+
const max = field.controlHostProps.max;
|
|
20423
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
20424
|
+
if (max === undefined || max === null || max === "") {
|
|
20405
20425
|
return null;
|
|
20406
20426
|
}
|
|
20427
|
+
const maxString = String(max);
|
|
20407
20428
|
const type = field.controlHostProps.type;
|
|
20408
20429
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20409
20430
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -20422,38 +20443,13 @@ const MAX_CONSTRAINT = {
|
|
|
20422
20443
|
if (numericValue <= maxNumber) {
|
|
20423
20444
|
return null;
|
|
20424
20445
|
}
|
|
20425
|
-
|
|
20426
|
-
if (naviInputType === "hour") {
|
|
20427
|
-
return naviI18n(`constraint.max.hour.default`, {
|
|
20428
|
-
max: maxString,
|
|
20429
|
-
});
|
|
20430
|
-
}
|
|
20431
|
-
if (naviInputType === "minute") {
|
|
20432
|
-
return naviI18n(`constraint.max.minute.default`, {
|
|
20433
|
-
max: maxString,
|
|
20434
|
-
});
|
|
20435
|
-
}
|
|
20436
|
-
if (naviInputType === "second") {
|
|
20437
|
-
return naviI18n(`constraint.max.second.default`, {
|
|
20438
|
-
max: maxString,
|
|
20439
|
-
});
|
|
20440
|
-
}
|
|
20441
|
-
if (naviInputType === "percentage") {
|
|
20442
|
-
return naviI18n(`constraint.max.percentage.default`, {
|
|
20443
|
-
max: maxString,
|
|
20444
|
-
});
|
|
20445
|
-
}
|
|
20446
|
-
return naviI18n(`constraint.max.number.default`, {
|
|
20447
|
-
max: maxString,
|
|
20448
|
-
});
|
|
20446
|
+
return boundMessage(field, "max");
|
|
20449
20447
|
}
|
|
20450
20448
|
if (type === "time") {
|
|
20451
20449
|
const [maxHours, maxMinutes] = maxString.split(":").map(Number);
|
|
20452
20450
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
20453
20451
|
if (hours > maxHours || (hours === maxHours && minutes > maxMinutes)) {
|
|
20454
|
-
return
|
|
20455
|
-
max: maxString,
|
|
20456
|
-
});
|
|
20452
|
+
return boundMessage(field, "max");
|
|
20457
20453
|
}
|
|
20458
20454
|
return null;
|
|
20459
20455
|
}
|
|
@@ -20461,17 +20457,11 @@ const MAX_CONSTRAINT = {
|
|
|
20461
20457
|
if (valueAsString <= maxString) {
|
|
20462
20458
|
return null;
|
|
20463
20459
|
}
|
|
20464
|
-
|
|
20465
|
-
const todayIso = getTodayIso(type);
|
|
20466
|
-
if (maxString === todayIso) {
|
|
20467
|
-
return naviI18n("constraint.max.date.today.default");
|
|
20468
|
-
}
|
|
20469
|
-
return naviI18n("constraint.max.date.default", {
|
|
20470
|
-
max: formatDateIso(maxString, type),
|
|
20471
|
-
});
|
|
20460
|
+
return boundMessage(field, "max");
|
|
20472
20461
|
}
|
|
20473
20462
|
return null;
|
|
20474
20463
|
},
|
|
20464
|
+
messageAtBound: (field) => boundMessage(field, "max"),
|
|
20475
20465
|
};
|
|
20476
20466
|
CONSTRAINT_ATTRIBUTE_SET.add("max");
|
|
20477
20467
|
|
|
@@ -20666,6 +20656,51 @@ const formatMsToTime = (ms, showSeconds) => {
|
|
|
20666
20656
|
return `${hh}:${mm}:${ss}`;
|
|
20667
20657
|
};
|
|
20668
20658
|
|
|
20659
|
+
// What a bound says, whatever the value: the sentence `check` gives a value
|
|
20660
|
+
// past it, and the one a spin gives when its chevron is refused there
|
|
20661
|
+
// (picker_spin.jsx) — one sentence, so the two refuse in the same words. null
|
|
20662
|
+
// for a type that has nothing to say about a bound.
|
|
20663
|
+
const boundMessage = (field, bound) => {
|
|
20664
|
+
const boundValue = field.controlHostProps[bound];
|
|
20665
|
+
if (field.controlType === "duration_group") {
|
|
20666
|
+
return naviI18n(`constraint.${bound}.duration.default`, {
|
|
20667
|
+
[bound]: formatDuration(boundValue),
|
|
20668
|
+
});
|
|
20669
|
+
}
|
|
20670
|
+
const type = field.controlHostProps.type;
|
|
20671
|
+
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20672
|
+
if (isNumberInput(type, naviInputType)) {
|
|
20673
|
+
const numberKind = NUMBER_BOUND_KIND_SET.has(naviInputType)
|
|
20674
|
+
? naviInputType
|
|
20675
|
+
: "number";
|
|
20676
|
+
return naviI18n(`constraint.${bound}.${numberKind}.default`, {
|
|
20677
|
+
[bound]: String(boundValue),
|
|
20678
|
+
});
|
|
20679
|
+
}
|
|
20680
|
+
if (type === "time") {
|
|
20681
|
+
return naviI18n(`constraint.${bound}.time.default`, {
|
|
20682
|
+
[bound]: boundValue,
|
|
20683
|
+
});
|
|
20684
|
+
}
|
|
20685
|
+
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
20686
|
+
if (boundValue === getTodayIso(type)) {
|
|
20687
|
+
return naviI18n(`constraint.${bound}.date.today.default`);
|
|
20688
|
+
}
|
|
20689
|
+
return naviI18n(`constraint.${bound}.date.default`, {
|
|
20690
|
+
[bound]: formatDateIso(boundValue, type),
|
|
20691
|
+
});
|
|
20692
|
+
}
|
|
20693
|
+
return null;
|
|
20694
|
+
};
|
|
20695
|
+
// The numbers whose bound is said with what they count ("Max 23 heures");
|
|
20696
|
+
// any other is "Ce nombre".
|
|
20697
|
+
const NUMBER_BOUND_KIND_SET = new Set([
|
|
20698
|
+
"hour",
|
|
20699
|
+
"minute",
|
|
20700
|
+
"second",
|
|
20701
|
+
"percentage",
|
|
20702
|
+
]);
|
|
20703
|
+
|
|
20669
20704
|
const getTodayIso = (inputType) => {
|
|
20670
20705
|
const now = new Date();
|
|
20671
20706
|
const yyyy = now.getFullYear();
|
|
@@ -29158,24 +29193,6 @@ const TYPE_CONVERTERS = {
|
|
|
29158
29193
|
},
|
|
29159
29194
|
};
|
|
29160
29195
|
|
|
29161
|
-
/**
|
|
29162
|
-
* What a component can learn about the page it is rendered in, from the route
|
|
29163
|
-
* container rendering that page (see useContentKeepingLeavingPages in
|
|
29164
|
-
* route.jsx). `null` outside of any route container.
|
|
29165
|
-
*
|
|
29166
|
-
* - `isShown()` — whether the container would still show the page. False as
|
|
29167
|
-
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
29168
|
-
* hidden until the route transition leaving it is over, and nothing it reads
|
|
29169
|
-
* is worth a render any more. False for good once it is kept while leaving: a
|
|
29170
|
-
* return to its route mounts the page anew. Asked outside any render — by a
|
|
29171
|
-
* subscription deciding whether to re-render — and it reads the routes
|
|
29172
|
-
* untracked, so asking subscribes nobody to them.
|
|
29173
|
-
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
29174
|
-
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
29175
|
-
* bar) steps out rather than showing with the page arriving.
|
|
29176
|
-
*/
|
|
29177
|
-
const RoutePageContext = createContext(null);
|
|
29178
|
-
|
|
29179
29196
|
const promiseStateWeakMap = new WeakMap();
|
|
29180
29197
|
const usePromiseAsyncData = (
|
|
29181
29198
|
promise,
|
|
@@ -34076,11 +34093,17 @@ const useContentKeepingLeavingPages = (branch, readShownBranch, parentPage) => {
|
|
|
34076
34093
|
}
|
|
34077
34094
|
}
|
|
34078
34095
|
});
|
|
34096
|
+
// Also run when a Suspense boundary above parks this container — a page
|
|
34097
|
+
// arriving that suspends to a `<Loading>` around both pages: every hook of
|
|
34098
|
+
// the pages kept while leaving has then been cleaned up, which is all that
|
|
34099
|
+
// keeping them saved. Dropped, so the render resuming the container takes
|
|
34100
|
+
// them down, rather than re-running every effect of a hidden page.
|
|
34079
34101
|
useLayoutEffect(() => {
|
|
34080
34102
|
return () => {
|
|
34081
34103
|
for (const entry of leavingRef.current) {
|
|
34082
34104
|
entry.hold.containers.delete(rerender);
|
|
34083
34105
|
}
|
|
34106
|
+
leavingRef.current = [];
|
|
34084
34107
|
};
|
|
34085
34108
|
}, []);
|
|
34086
34109
|
if (leaving.length === 0) {
|
|
@@ -83172,6 +83195,8 @@ const TRAVELS_NONE = [];
|
|
|
83172
83195
|
* name?: string,
|
|
83173
83196
|
* min?: any,
|
|
83174
83197
|
* max?: any,
|
|
83198
|
+
* minMessage?: any,
|
|
83199
|
+
* maxMessage?: any,
|
|
83175
83200
|
* step?: number,
|
|
83176
83201
|
* type?: string,
|
|
83177
83202
|
* editable?: boolean,
|
|
@@ -83210,7 +83235,15 @@ const TRAVELS_NONE = [];
|
|
|
83210
83235
|
* @param {number} [step=1] How many steps a press covers.
|
|
83211
83236
|
* @param {number} [duration=250] How long a travel takes, in milliseconds.
|
|
83212
83237
|
* @param {any} [min] The first value one can reach; `max` is the last. Beyond
|
|
83213
|
-
* them the travel simply does not happen and the chevron that way says so
|
|
83238
|
+
* them the travel simply does not happen and the chevron that way says so,
|
|
83239
|
+
* in the words the control in the middle uses for a value past that end ("La
|
|
83240
|
+
* date doit être au plus tard le 2 avril 2027", "Max 5"). A value the control
|
|
83241
|
+
* has no such sentence for — what a plain `Spin` steps through — is "nothing
|
|
83242
|
+
* after this one".
|
|
83243
|
+
* @param {any} [maxMessage] What is said at `max` instead: by the chevron
|
|
83244
|
+
* refused there, by a travel asked past it, and by the control in the middle
|
|
83245
|
+
* for a value past it (picked in the calendar, typed). `minMessage` is its
|
|
83246
|
+
* twin at `min`.
|
|
83214
83247
|
* @param {string} [padding] The room around the value, `paddingX`/`paddingY`
|
|
83215
83248
|
* and the four sides included. It goes on what is inside the box rather than
|
|
83216
83249
|
* on the box: above and below it is taken by the value AND by the two
|
|
@@ -83240,6 +83273,8 @@ const Spin = ({
|
|
|
83240
83273
|
name,
|
|
83241
83274
|
min,
|
|
83242
83275
|
max,
|
|
83276
|
+
minMessage,
|
|
83277
|
+
maxMessage,
|
|
83243
83278
|
step = 1,
|
|
83244
83279
|
duration = 250,
|
|
83245
83280
|
type = "text",
|
|
@@ -83365,15 +83400,31 @@ const Spin = ({
|
|
|
83365
83400
|
const readOnlyResolved = Boolean(readOnly || readOnlyFromAbove || loadingResolved);
|
|
83366
83401
|
const disabledResolved = Boolean(disabled || disabledFromAbove);
|
|
83367
83402
|
|
|
83403
|
+
// The end of what one may reach, said the way the control in the middle
|
|
83404
|
+
// says it about a value past that end — its `minMessage`/`maxMessage`, or the
|
|
83405
|
+
// sentence its type has for the bound — so a chevron, the calendar and a
|
|
83406
|
+
// typed value refuse in the same words. Asked of the control at the press:
|
|
83407
|
+
// it is not there yet on the first render. `goingNext` is towards the bigger
|
|
83408
|
+
// values, whichever end of the box that is.
|
|
83409
|
+
const boundMessage = goingNext => {
|
|
83410
|
+
const constraint = goingNext ? MAX_CONSTRAINT : MIN_CONSTRAINT;
|
|
83411
|
+
const controlEl = controlRef.current;
|
|
83412
|
+
const controller = (findControlHost(controlEl) || controlEl).__uiStateController__;
|
|
83413
|
+
const {
|
|
83414
|
+
message
|
|
83415
|
+
} = getConstraintMessage(controller, constraint, constraint.messageAtBound(controller), {});
|
|
83416
|
+
return message ?? naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before");
|
|
83417
|
+
};
|
|
83418
|
+
|
|
83368
83419
|
// Why a chevron refuses, in its own words — and only when the reason is
|
|
83369
83420
|
// ours: the end of what one may reach is something this control knows and
|
|
83370
83421
|
// navi cannot guess. For everything else the reason belongs to the state the
|
|
83371
83422
|
// whole control is in, and it is said about the control ("read-only",
|
|
83372
83423
|
// "busy") rather than about the button, which is not what the user was
|
|
83373
83424
|
// pressing.
|
|
83374
|
-
const wayOutMessage = (allowed,
|
|
83425
|
+
const wayOutMessage = (allowed, isNext) => {
|
|
83375
83426
|
if (!allowed) {
|
|
83376
|
-
return
|
|
83427
|
+
return boundMessage(isNext);
|
|
83377
83428
|
}
|
|
83378
83429
|
if (loadingResolved) {
|
|
83379
83430
|
return naviI18n("constraint.busy.default");
|
|
@@ -83485,7 +83536,7 @@ const Spin = ({
|
|
|
83485
83536
|
// answer belongs.
|
|
83486
83537
|
e.preventDefault();
|
|
83487
83538
|
e.stopPropagation();
|
|
83488
|
-
openCallout(
|
|
83539
|
+
openCallout(boundMessage(goingNext), {
|
|
83489
83540
|
anchorElement: middleRef.current,
|
|
83490
83541
|
status: "info",
|
|
83491
83542
|
openingEvent: e.detail.event ?? e
|
|
@@ -83544,12 +83595,14 @@ const Spin = ({
|
|
|
83544
83595
|
const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
|
|
83545
83596
|
const wayOut = atStart => {
|
|
83546
83597
|
const isNext = atStart ? startIsNext : !startIsNext;
|
|
83598
|
+
const allowed = atStart ? startAllowed : endAllowed;
|
|
83547
83599
|
return jsx(WayOut, {
|
|
83548
83600
|
atStart: atStart,
|
|
83549
83601
|
onPointerDown: e => {
|
|
83550
83602
|
wayOutPointerTypeRef.current = e.pointerType;
|
|
83551
83603
|
},
|
|
83552
|
-
|
|
83604
|
+
unavailable: !allowed || loadingResolved || readOnlyResolved,
|
|
83605
|
+
unavailableMessage: () => wayOutMessage(allowed, isNext),
|
|
83553
83606
|
label: isNext ? nextLabel ?? naviI18n("spin.next") : previousLabel ?? naviI18n("spin.previous"),
|
|
83554
83607
|
onPress: e => {
|
|
83555
83608
|
focusMiddle();
|
|
@@ -83596,6 +83649,8 @@ const Spin = ({
|
|
|
83596
83649
|
...valueProps,
|
|
83597
83650
|
min: min,
|
|
83598
83651
|
max: max,
|
|
83652
|
+
minMessage: minMessage,
|
|
83653
|
+
maxMessage: maxMessage,
|
|
83599
83654
|
step: step,
|
|
83600
83655
|
readOnly: readOnly,
|
|
83601
83656
|
disabled: disabled,
|
|
@@ -83629,6 +83684,8 @@ const Spin = ({
|
|
|
83629
83684
|
...valueProps,
|
|
83630
83685
|
min: min,
|
|
83631
83686
|
max: max,
|
|
83687
|
+
minMessage: minMessage,
|
|
83688
|
+
maxMessage: maxMessage,
|
|
83632
83689
|
readOnly: readOnly,
|
|
83633
83690
|
disabled: disabled,
|
|
83634
83691
|
loading: loading,
|
|
@@ -83739,6 +83796,7 @@ const Spin = ({
|
|
|
83739
83796
|
const WayOut = ({
|
|
83740
83797
|
atStart,
|
|
83741
83798
|
commandFor,
|
|
83799
|
+
unavailable,
|
|
83742
83800
|
unavailableMessage,
|
|
83743
83801
|
label,
|
|
83744
83802
|
onPress,
|
|
@@ -83749,10 +83807,10 @@ const WayOut = ({
|
|
|
83749
83807
|
// onPointerUp below).
|
|
83750
83808
|
const pointerDownRef = useRef(null);
|
|
83751
83809
|
const press = e => {
|
|
83752
|
-
if (
|
|
83810
|
+
if (unavailable) {
|
|
83753
83811
|
// Why it does nothing, said where one pressed: a control would have
|
|
83754
83812
|
// done this through its own interaction gate, and this one has none.
|
|
83755
|
-
openCallout(unavailableMessage, {
|
|
83813
|
+
openCallout(unavailableMessage(), {
|
|
83756
83814
|
anchorElement: e.currentTarget,
|
|
83757
83815
|
status: "info",
|
|
83758
83816
|
openingEvent: e
|
|
@@ -83779,8 +83837,8 @@ const WayOut = ({
|
|
|
83779
83837
|
,
|
|
83780
83838
|
role: "button",
|
|
83781
83839
|
"aria-label": label,
|
|
83782
|
-
"aria-disabled":
|
|
83783
|
-
"data-unavailable":
|
|
83840
|
+
"aria-disabled": unavailable ? "true" : undefined,
|
|
83841
|
+
"data-unavailable": unavailable ? "" : undefined
|
|
83784
83842
|
// At the chevron, not beside it: a callout aims its arrow at where the
|
|
83785
83843
|
// anchor's text starts, and there is no text here — only a glyph in the
|
|
83786
83844
|
// middle of the box, which is what one pressed and what the answer is
|
|
@@ -84013,6 +84071,8 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
84013
84071
|
* defaultValue?: number|string,
|
|
84014
84072
|
* min?: number,
|
|
84015
84073
|
* max?: number,
|
|
84074
|
+
* minMessage?: any,
|
|
84075
|
+
* maxMessage?: any,
|
|
84016
84076
|
* step?: number,
|
|
84017
84077
|
* pad?: number,
|
|
84018
84078
|
* loop?: boolean,
|
|
@@ -84118,6 +84178,8 @@ const numberAtStep = (value, count, {
|
|
|
84118
84178
|
* name?: string,
|
|
84119
84179
|
* min?: string,
|
|
84120
84180
|
* max?: string,
|
|
84181
|
+
* minMessage?: any,
|
|
84182
|
+
* maxMessage?: any,
|
|
84121
84183
|
* step?: number,
|
|
84122
84184
|
* lang?: string,
|
|
84123
84185
|
* renderDay?: (day: string) => import("ignore:preact").ComponentChildren,
|