@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/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, useCallback,
|
|
10
|
+
import { useErrorBoundary, useLayoutEffect, useContext, useState, useMemo, useRef, useReducer, useEffect, useCallback, useId } from "preact/hooks";
|
|
11
11
|
import { humanizeI18n, 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
|
|
|
@@ -944,8 +944,10 @@ naviI18n.addAll({
|
|
|
944
944
|
},
|
|
945
945
|
});
|
|
946
946
|
|
|
947
|
-
// Spin messages — the ends of what one steps through
|
|
948
|
-
//
|
|
947
|
+
// Spin messages — the ends of what one steps through when the control holding
|
|
948
|
+
// it has no sentence of its own for a bound (a day or a number says its
|
|
949
|
+
// constraint.min/max one, see picker_spin.jsx): said without naming what it is
|
|
950
|
+
// made of, so they fit pages, sizes or any list of items.
|
|
949
951
|
naviI18n.addAll({
|
|
950
952
|
"spin.previous": {
|
|
951
953
|
en: "Previous",
|
|
@@ -10779,6 +10781,42 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
10779
10781
|
return `${urlObject.origin}${urlObject.pathname}`;
|
|
10780
10782
|
};
|
|
10781
10783
|
|
|
10784
|
+
/**
|
|
10785
|
+
* What a component can learn about the page it is rendered in, from the route
|
|
10786
|
+
* container rendering that page (see useContentKeepingLeavingPages in
|
|
10787
|
+
* route.jsx). `null` outside of any route container.
|
|
10788
|
+
*
|
|
10789
|
+
* - `isShown()` — whether the container would still show the page. False as
|
|
10790
|
+
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
10791
|
+
* hidden until the route transition leaving it is over, and nothing it reads
|
|
10792
|
+
* is worth a render any more. False for good once it is kept while leaving: a
|
|
10793
|
+
* return to its route mounts the page anew. Asked outside any render — by a
|
|
10794
|
+
* subscription deciding whether to re-render — and it reads the routes
|
|
10795
|
+
* untracked, so asking subscribes nobody to them.
|
|
10796
|
+
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
10797
|
+
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
10798
|
+
* bar) steps out rather than showing with the page arriving.
|
|
10799
|
+
*/
|
|
10800
|
+
const RoutePageContext = createContext(null);
|
|
10801
|
+
|
|
10802
|
+
/**
|
|
10803
|
+
* A container has put its page on screen — or as much of it as it can.
|
|
10804
|
+
*
|
|
10805
|
+
* A route matching is a signal changing, and the page it selects reaches the
|
|
10806
|
+
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
10807
|
+
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
10808
|
+
* as it has been decided (a travel about to have its picture taken by the
|
|
10809
|
+
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
10810
|
+
*
|
|
10811
|
+
* A page waiting on data is announced too, by the boundary showing its loading
|
|
10812
|
+
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
10813
|
+
* screen is what the browser is about to take a picture of, and a page that
|
|
10814
|
+
* cannot render yet would otherwise be waited on until the transition dies of
|
|
10815
|
+
* it. It lives in a module of its own for that: the async layer says it as much
|
|
10816
|
+
* as the router does, and neither can import the other.
|
|
10817
|
+
*/
|
|
10818
|
+
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
10819
|
+
|
|
10782
10820
|
const documentStateSignal = signal(null);
|
|
10783
10821
|
const useDocumentState = () => {
|
|
10784
10822
|
return documentStateSignal.value;
|
|
@@ -11038,24 +11076,6 @@ const readTopInWindow = element => {
|
|
|
11038
11076
|
return element.getBoundingClientRect().top;
|
|
11039
11077
|
};
|
|
11040
11078
|
|
|
11041
|
-
/**
|
|
11042
|
-
* A container has put its page on screen — or as much of it as it can.
|
|
11043
|
-
*
|
|
11044
|
-
* A route matching is a signal changing, and the page it selects reaches the
|
|
11045
|
-
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
11046
|
-
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
11047
|
-
* as it has been decided (a travel about to have its picture taken by the
|
|
11048
|
-
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
11049
|
-
*
|
|
11050
|
-
* A page waiting on data is announced too, by the boundary showing its loading
|
|
11051
|
-
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
11052
|
-
* screen is what the browser is about to take a picture of, and a page that
|
|
11053
|
-
* cannot render yet would otherwise be waited on until the transition dies of
|
|
11054
|
-
* it. It lives in a module of its own for that: the async layer says it as much
|
|
11055
|
-
* as the router does, and neither can import the other.
|
|
11056
|
-
*/
|
|
11057
|
-
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
11058
|
-
|
|
11059
11079
|
/**
|
|
11060
11080
|
* Where a page was left, given back when one comes back to it.
|
|
11061
11081
|
*
|
|
@@ -12450,6 +12470,7 @@ browserIntegration.handleActionTask;
|
|
|
12450
12470
|
|
|
12451
12471
|
const NO_OP = () => {};
|
|
12452
12472
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
12473
|
+
const countRenders$1 = (count) => count + 1;
|
|
12453
12474
|
// What the computed below answers for a key the document state does not hold:
|
|
12454
12475
|
// key presence is the flag, and the value under a present key may be anything,
|
|
12455
12476
|
// undefined included.
|
|
@@ -12478,7 +12499,36 @@ const useNavStateBasic = (
|
|
|
12478
12499
|
[id],
|
|
12479
12500
|
);
|
|
12480
12501
|
const keyValue = keyValueComputed.value;
|
|
12481
|
-
|
|
12502
|
+
// A page the address has left keeps reading the entry it was shown on until
|
|
12503
|
+
// it is taken down (kept hidden while a route transition plays): the new
|
|
12504
|
+
// entry would close what was open there — a Picker as a cancel — where the
|
|
12505
|
+
// same page taken down at once is only unmounted. Its render still happens:
|
|
12506
|
+
// the state is written before the routes say the page is left.
|
|
12507
|
+
const routePage = useContext(RoutePageContext);
|
|
12508
|
+
const keyValueReadRef = useRef(keyValue);
|
|
12509
|
+
const keyValueWithheld =
|
|
12510
|
+
keyValue !== keyValueReadRef.current &&
|
|
12511
|
+
routePage !== null &&
|
|
12512
|
+
!routePage.isShown();
|
|
12513
|
+
if (!keyValueWithheld) {
|
|
12514
|
+
keyValueReadRef.current = keyValue;
|
|
12515
|
+
}
|
|
12516
|
+
const keyValueRead = keyValueReadRef.current;
|
|
12517
|
+
// Owed rather than dropped, as in useAsyncData: the address can come back
|
|
12518
|
+
// before the container renders (a redirect straight back), and the page then
|
|
12519
|
+
// stays on screen.
|
|
12520
|
+
const [, rerender] = useReducer(countRenders$1, 0);
|
|
12521
|
+
useEffect(() => {
|
|
12522
|
+
if (!keyValueWithheld) {
|
|
12523
|
+
return undefined;
|
|
12524
|
+
}
|
|
12525
|
+
return observeRouteRender(() => {
|
|
12526
|
+
if (routePage.isShown()) {
|
|
12527
|
+
rerender();
|
|
12528
|
+
}
|
|
12529
|
+
});
|
|
12530
|
+
}, [keyValueWithheld]);
|
|
12531
|
+
const keyInState = keyValueRead !== KEY_NOT_IN_STATE;
|
|
12482
12532
|
const onLeaveRef = useRef(onLeave);
|
|
12483
12533
|
onLeaveRef.current = onLeave;
|
|
12484
12534
|
const prevKeyInStateRef = useRef(keyInState);
|
|
@@ -12508,7 +12558,7 @@ const useNavStateBasic = (
|
|
|
12508
12558
|
effectiveType = "replace";
|
|
12509
12559
|
}
|
|
12510
12560
|
|
|
12511
|
-
const currentValue = keyInState ?
|
|
12561
|
+
const currentValue = keyInState ? keyValueRead : defaultValue;
|
|
12512
12562
|
|
|
12513
12563
|
if (debug) {
|
|
12514
12564
|
console.debug(`useNavState(${id}) current value is ${currentValue}`);
|
|
@@ -12576,6 +12626,8 @@ const useNavStateBasic = (
|
|
|
12576
12626
|
/**
|
|
12577
12627
|
* Stores a named value in the browser's document state and returns it reactively.
|
|
12578
12628
|
* The component re-renders whenever the value changes (navigation, back/forward button).
|
|
12629
|
+
* In a page the address has left — kept hidden while a route transition plays —
|
|
12630
|
+
* the value stays the one of the entry that page was shown on.
|
|
12579
12631
|
*
|
|
12580
12632
|
* @param {string} id
|
|
12581
12633
|
* Unique key used to store the value in document state. Must be stable across renders.
|
|
@@ -19553,18 +19605,17 @@ const MIN_CONSTRAINT = {
|
|
|
19553
19605
|
if (cmp === null || cmp >= 0) {
|
|
19554
19606
|
return null;
|
|
19555
19607
|
}
|
|
19556
|
-
|
|
19557
|
-
return naviI18n("constraint.min.duration.default", {
|
|
19558
|
-
min: formatDuration(min),
|
|
19559
|
-
});
|
|
19608
|
+
return boundMessage(field, "min");
|
|
19560
19609
|
}
|
|
19561
19610
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
19562
19611
|
return null;
|
|
19563
19612
|
}
|
|
19564
|
-
const
|
|
19565
|
-
|
|
19613
|
+
const min = field.controlHostProps.min;
|
|
19614
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
19615
|
+
if (min === undefined || min === null || min === "") {
|
|
19566
19616
|
return null;
|
|
19567
19617
|
}
|
|
19618
|
+
const minString = String(min);
|
|
19568
19619
|
const type = field.controlHostProps.type;
|
|
19569
19620
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
19570
19621
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -19580,40 +19631,16 @@ const MIN_CONSTRAINT = {
|
|
|
19580
19631
|
if (isNaN(numericValue)) {
|
|
19581
19632
|
return null;
|
|
19582
19633
|
}
|
|
19583
|
-
if (numericValue
|
|
19584
|
-
|
|
19585
|
-
return naviI18n(`constraint.min.hour.default`, {
|
|
19586
|
-
min: minString,
|
|
19587
|
-
});
|
|
19588
|
-
}
|
|
19589
|
-
if (naviInputType === "minute") {
|
|
19590
|
-
return naviI18n(`constraint.min.minute.default`, {
|
|
19591
|
-
min: minString,
|
|
19592
|
-
});
|
|
19593
|
-
}
|
|
19594
|
-
if (naviInputType === "second") {
|
|
19595
|
-
return naviI18n(`constraint.min.second.default`, {
|
|
19596
|
-
min: minString,
|
|
19597
|
-
});
|
|
19598
|
-
}
|
|
19599
|
-
if (naviInputType === "percentage") {
|
|
19600
|
-
return naviI18n(`constraint.min.percentage.default`, {
|
|
19601
|
-
min: minString,
|
|
19602
|
-
});
|
|
19603
|
-
}
|
|
19604
|
-
return naviI18n(`constraint.min.number.default`, {
|
|
19605
|
-
min: minString,
|
|
19606
|
-
});
|
|
19634
|
+
if (numericValue >= minNumber) {
|
|
19635
|
+
return null;
|
|
19607
19636
|
}
|
|
19608
|
-
return
|
|
19637
|
+
return boundMessage(field, "min");
|
|
19609
19638
|
}
|
|
19610
19639
|
if (type === "time") {
|
|
19611
19640
|
const [minHours, minMinutes] = minString.split(":").map(Number);
|
|
19612
19641
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
19613
19642
|
if (hours < minHours || (hours === minHours && minutes < minMinutes)) {
|
|
19614
|
-
return
|
|
19615
|
-
min: minString,
|
|
19616
|
-
});
|
|
19643
|
+
return boundMessage(field, "min");
|
|
19617
19644
|
}
|
|
19618
19645
|
return null;
|
|
19619
19646
|
}
|
|
@@ -19621,18 +19648,13 @@ const MIN_CONSTRAINT = {
|
|
|
19621
19648
|
// so they never need a min/max validation message.
|
|
19622
19649
|
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
19623
19650
|
if (valueAsString < minString) {
|
|
19624
|
-
|
|
19625
|
-
if (minString === todayIso) {
|
|
19626
|
-
return naviI18n("constraint.min.date.today.default");
|
|
19627
|
-
}
|
|
19628
|
-
return naviI18n("constraint.min.date.default", {
|
|
19629
|
-
min: formatDateIso(minString, type),
|
|
19630
|
-
});
|
|
19651
|
+
return boundMessage(field, "min");
|
|
19631
19652
|
}
|
|
19632
19653
|
return null;
|
|
19633
19654
|
}
|
|
19634
19655
|
return null;
|
|
19635
19656
|
},
|
|
19657
|
+
messageAtBound: (field) => boundMessage(field, "min"),
|
|
19636
19658
|
};
|
|
19637
19659
|
CONSTRAINT_ATTRIBUTE_SET.add("min");
|
|
19638
19660
|
|
|
@@ -19652,18 +19674,17 @@ const MAX_CONSTRAINT = {
|
|
|
19652
19674
|
if (cmp === null || cmp <= 0) {
|
|
19653
19675
|
return null;
|
|
19654
19676
|
}
|
|
19655
|
-
|
|
19656
|
-
return naviI18n("constraint.max.duration.default", {
|
|
19657
|
-
max: formatDuration(max),
|
|
19658
|
-
});
|
|
19677
|
+
return boundMessage(field, "max");
|
|
19659
19678
|
}
|
|
19660
19679
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
19661
19680
|
return null;
|
|
19662
19681
|
}
|
|
19663
|
-
const
|
|
19664
|
-
|
|
19682
|
+
const max = field.controlHostProps.max;
|
|
19683
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
19684
|
+
if (max === undefined || max === null || max === "") {
|
|
19665
19685
|
return null;
|
|
19666
19686
|
}
|
|
19687
|
+
const maxString = String(max);
|
|
19667
19688
|
const type = field.controlHostProps.type;
|
|
19668
19689
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
19669
19690
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -19682,38 +19703,13 @@ const MAX_CONSTRAINT = {
|
|
|
19682
19703
|
if (numericValue <= maxNumber) {
|
|
19683
19704
|
return null;
|
|
19684
19705
|
}
|
|
19685
|
-
|
|
19686
|
-
if (naviInputType === "hour") {
|
|
19687
|
-
return naviI18n(`constraint.max.hour.default`, {
|
|
19688
|
-
max: maxString,
|
|
19689
|
-
});
|
|
19690
|
-
}
|
|
19691
|
-
if (naviInputType === "minute") {
|
|
19692
|
-
return naviI18n(`constraint.max.minute.default`, {
|
|
19693
|
-
max: maxString,
|
|
19694
|
-
});
|
|
19695
|
-
}
|
|
19696
|
-
if (naviInputType === "second") {
|
|
19697
|
-
return naviI18n(`constraint.max.second.default`, {
|
|
19698
|
-
max: maxString,
|
|
19699
|
-
});
|
|
19700
|
-
}
|
|
19701
|
-
if (naviInputType === "percentage") {
|
|
19702
|
-
return naviI18n(`constraint.max.percentage.default`, {
|
|
19703
|
-
max: maxString,
|
|
19704
|
-
});
|
|
19705
|
-
}
|
|
19706
|
-
return naviI18n(`constraint.max.number.default`, {
|
|
19707
|
-
max: maxString,
|
|
19708
|
-
});
|
|
19706
|
+
return boundMessage(field, "max");
|
|
19709
19707
|
}
|
|
19710
19708
|
if (type === "time") {
|
|
19711
19709
|
const [maxHours, maxMinutes] = maxString.split(":").map(Number);
|
|
19712
19710
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
19713
19711
|
if (hours > maxHours || (hours === maxHours && minutes > maxMinutes)) {
|
|
19714
|
-
return
|
|
19715
|
-
max: maxString,
|
|
19716
|
-
});
|
|
19712
|
+
return boundMessage(field, "max");
|
|
19717
19713
|
}
|
|
19718
19714
|
return null;
|
|
19719
19715
|
}
|
|
@@ -19721,17 +19717,11 @@ const MAX_CONSTRAINT = {
|
|
|
19721
19717
|
if (valueAsString <= maxString) {
|
|
19722
19718
|
return null;
|
|
19723
19719
|
}
|
|
19724
|
-
|
|
19725
|
-
const todayIso = getTodayIso(type);
|
|
19726
|
-
if (maxString === todayIso) {
|
|
19727
|
-
return naviI18n("constraint.max.date.today.default");
|
|
19728
|
-
}
|
|
19729
|
-
return naviI18n("constraint.max.date.default", {
|
|
19730
|
-
max: formatDateIso(maxString, type),
|
|
19731
|
-
});
|
|
19720
|
+
return boundMessage(field, "max");
|
|
19732
19721
|
}
|
|
19733
19722
|
return null;
|
|
19734
19723
|
},
|
|
19724
|
+
messageAtBound: (field) => boundMessage(field, "max"),
|
|
19735
19725
|
};
|
|
19736
19726
|
CONSTRAINT_ATTRIBUTE_SET.add("max");
|
|
19737
19727
|
|
|
@@ -19926,6 +19916,51 @@ const formatMsToTime = (ms, showSeconds) => {
|
|
|
19926
19916
|
return `${hh}:${mm}:${ss}`;
|
|
19927
19917
|
};
|
|
19928
19918
|
|
|
19919
|
+
// What a bound says, whatever the value: the sentence `check` gives a value
|
|
19920
|
+
// past it, and the one a spin gives when its chevron is refused there
|
|
19921
|
+
// (picker_spin.jsx) — one sentence, so the two refuse in the same words. null
|
|
19922
|
+
// for a type that has nothing to say about a bound.
|
|
19923
|
+
const boundMessage = (field, bound) => {
|
|
19924
|
+
const boundValue = field.controlHostProps[bound];
|
|
19925
|
+
if (field.controlType === "duration_group") {
|
|
19926
|
+
return naviI18n(`constraint.${bound}.duration.default`, {
|
|
19927
|
+
[bound]: formatDuration(boundValue),
|
|
19928
|
+
});
|
|
19929
|
+
}
|
|
19930
|
+
const type = field.controlHostProps.type;
|
|
19931
|
+
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
19932
|
+
if (isNumberInput(type, naviInputType)) {
|
|
19933
|
+
const numberKind = NUMBER_BOUND_KIND_SET.has(naviInputType)
|
|
19934
|
+
? naviInputType
|
|
19935
|
+
: "number";
|
|
19936
|
+
return naviI18n(`constraint.${bound}.${numberKind}.default`, {
|
|
19937
|
+
[bound]: String(boundValue),
|
|
19938
|
+
});
|
|
19939
|
+
}
|
|
19940
|
+
if (type === "time") {
|
|
19941
|
+
return naviI18n(`constraint.${bound}.time.default`, {
|
|
19942
|
+
[bound]: boundValue,
|
|
19943
|
+
});
|
|
19944
|
+
}
|
|
19945
|
+
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
19946
|
+
if (boundValue === getTodayIso(type)) {
|
|
19947
|
+
return naviI18n(`constraint.${bound}.date.today.default`);
|
|
19948
|
+
}
|
|
19949
|
+
return naviI18n(`constraint.${bound}.date.default`, {
|
|
19950
|
+
[bound]: formatDateIso(boundValue, type),
|
|
19951
|
+
});
|
|
19952
|
+
}
|
|
19953
|
+
return null;
|
|
19954
|
+
};
|
|
19955
|
+
// The numbers whose bound is said with what they count ("Max 23 heures");
|
|
19956
|
+
// any other is "Ce nombre".
|
|
19957
|
+
const NUMBER_BOUND_KIND_SET = new Set([
|
|
19958
|
+
"hour",
|
|
19959
|
+
"minute",
|
|
19960
|
+
"second",
|
|
19961
|
+
"percentage",
|
|
19962
|
+
]);
|
|
19963
|
+
|
|
19929
19964
|
const getTodayIso = (inputType) => {
|
|
19930
19965
|
const now = new Date();
|
|
19931
19966
|
const yyyy = now.getFullYear();
|
|
@@ -28261,24 +28296,6 @@ const TYPE_CONVERTERS = {
|
|
|
28261
28296
|
},
|
|
28262
28297
|
};
|
|
28263
28298
|
|
|
28264
|
-
/**
|
|
28265
|
-
* What a component can learn about the page it is rendered in, from the route
|
|
28266
|
-
* container rendering that page (see useContentKeepingLeavingPages in
|
|
28267
|
-
* route.jsx). `null` outside of any route container.
|
|
28268
|
-
*
|
|
28269
|
-
* - `isShown()` — whether the container would still show the page. False as
|
|
28270
|
-
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
28271
|
-
* hidden until the route transition leaving it is over, and nothing it reads
|
|
28272
|
-
* is worth a render any more. False for good once it is kept while leaving: a
|
|
28273
|
-
* return to its route mounts the page anew. Asked outside any render — by a
|
|
28274
|
-
* subscription deciding whether to re-render — and it reads the routes
|
|
28275
|
-
* untracked, so asking subscribes nobody to them.
|
|
28276
|
-
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
28277
|
-
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
28278
|
-
* bar) steps out rather than showing with the page arriving.
|
|
28279
|
-
*/
|
|
28280
|
-
const RoutePageContext = createContext(null);
|
|
28281
|
-
|
|
28282
28299
|
const promiseStateWeakMap = new WeakMap();
|
|
28283
28300
|
const usePromiseAsyncData = (
|
|
28284
28301
|
promise,
|
|
@@ -33120,11 +33137,17 @@ const useContentKeepingLeavingPages = (branch, readShownBranch, parentPage) => {
|
|
|
33120
33137
|
}
|
|
33121
33138
|
}
|
|
33122
33139
|
});
|
|
33140
|
+
// Also run when a Suspense boundary above parks this container — a page
|
|
33141
|
+
// arriving that suspends to a `<Loading>` around both pages: every hook of
|
|
33142
|
+
// the pages kept while leaving has then been cleaned up, which is all that
|
|
33143
|
+
// keeping them saved. Dropped, so the render resuming the container takes
|
|
33144
|
+
// them down, rather than re-running every effect of a hidden page.
|
|
33123
33145
|
useLayoutEffect(() => {
|
|
33124
33146
|
return () => {
|
|
33125
33147
|
for (const entry of leavingRef.current) {
|
|
33126
33148
|
entry.hold.containers.delete(rerender);
|
|
33127
33149
|
}
|
|
33150
|
+
leavingRef.current = [];
|
|
33128
33151
|
};
|
|
33129
33152
|
}, []);
|
|
33130
33153
|
if (leaving.length === 0) {
|
|
@@ -80965,6 +80988,8 @@ const TRAVELS_NONE = [];
|
|
|
80965
80988
|
* name?: string,
|
|
80966
80989
|
* min?: any,
|
|
80967
80990
|
* max?: any,
|
|
80991
|
+
* minMessage?: any,
|
|
80992
|
+
* maxMessage?: any,
|
|
80968
80993
|
* step?: number,
|
|
80969
80994
|
* type?: string,
|
|
80970
80995
|
* editable?: boolean,
|
|
@@ -81003,7 +81028,15 @@ const TRAVELS_NONE = [];
|
|
|
81003
81028
|
* @param {number} [step=1] How many steps a press covers.
|
|
81004
81029
|
* @param {number} [duration=250] How long a travel takes, in milliseconds.
|
|
81005
81030
|
* @param {any} [min] The first value one can reach; `max` is the last. Beyond
|
|
81006
|
-
* them the travel simply does not happen and the chevron that way says so
|
|
81031
|
+
* them the travel simply does not happen and the chevron that way says so,
|
|
81032
|
+
* in the words the control in the middle uses for a value past that end ("La
|
|
81033
|
+
* date doit être au plus tard le 2 avril 2027", "Max 5"). A value the control
|
|
81034
|
+
* has no such sentence for — what a plain `Spin` steps through — is "nothing
|
|
81035
|
+
* after this one".
|
|
81036
|
+
* @param {any} [maxMessage] What is said at `max` instead: by the chevron
|
|
81037
|
+
* refused there, by a travel asked past it, and by the control in the middle
|
|
81038
|
+
* for a value past it (picked in the calendar, typed). `minMessage` is its
|
|
81039
|
+
* twin at `min`.
|
|
81007
81040
|
* @param {string} [padding] The room around the value, `paddingX`/`paddingY`
|
|
81008
81041
|
* and the four sides included. It goes on what is inside the box rather than
|
|
81009
81042
|
* on the box: above and below it is taken by the value AND by the two
|
|
@@ -81033,6 +81066,8 @@ const Spin = ({
|
|
|
81033
81066
|
name,
|
|
81034
81067
|
min,
|
|
81035
81068
|
max,
|
|
81069
|
+
minMessage,
|
|
81070
|
+
maxMessage,
|
|
81036
81071
|
step = 1,
|
|
81037
81072
|
duration = 250,
|
|
81038
81073
|
type = "text",
|
|
@@ -81158,15 +81193,31 @@ const Spin = ({
|
|
|
81158
81193
|
const readOnlyResolved = Boolean(readOnly || readOnlyFromAbove || loadingResolved);
|
|
81159
81194
|
const disabledResolved = Boolean(disabled || disabledFromAbove);
|
|
81160
81195
|
|
|
81196
|
+
// The end of what one may reach, said the way the control in the middle
|
|
81197
|
+
// says it about a value past that end — its `minMessage`/`maxMessage`, or the
|
|
81198
|
+
// sentence its type has for the bound — so a chevron, the calendar and a
|
|
81199
|
+
// typed value refuse in the same words. Asked of the control at the press:
|
|
81200
|
+
// it is not there yet on the first render. `goingNext` is towards the bigger
|
|
81201
|
+
// values, whichever end of the box that is.
|
|
81202
|
+
const boundMessage = goingNext => {
|
|
81203
|
+
const constraint = goingNext ? MAX_CONSTRAINT : MIN_CONSTRAINT;
|
|
81204
|
+
const controlEl = controlRef.current;
|
|
81205
|
+
const controller = (findControlHost(controlEl) || controlEl).__uiStateController__;
|
|
81206
|
+
const {
|
|
81207
|
+
message
|
|
81208
|
+
} = getConstraintMessage(controller, constraint, constraint.messageAtBound(controller), {});
|
|
81209
|
+
return message ?? naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before");
|
|
81210
|
+
};
|
|
81211
|
+
|
|
81161
81212
|
// Why a chevron refuses, in its own words — and only when the reason is
|
|
81162
81213
|
// ours: the end of what one may reach is something this control knows and
|
|
81163
81214
|
// navi cannot guess. For everything else the reason belongs to the state the
|
|
81164
81215
|
// whole control is in, and it is said about the control ("read-only",
|
|
81165
81216
|
// "busy") rather than about the button, which is not what the user was
|
|
81166
81217
|
// pressing.
|
|
81167
|
-
const wayOutMessage = (allowed,
|
|
81218
|
+
const wayOutMessage = (allowed, isNext) => {
|
|
81168
81219
|
if (!allowed) {
|
|
81169
|
-
return
|
|
81220
|
+
return boundMessage(isNext);
|
|
81170
81221
|
}
|
|
81171
81222
|
if (loadingResolved) {
|
|
81172
81223
|
return naviI18n("constraint.busy.default");
|
|
@@ -81278,7 +81329,7 @@ const Spin = ({
|
|
|
81278
81329
|
// answer belongs.
|
|
81279
81330
|
e.preventDefault();
|
|
81280
81331
|
e.stopPropagation();
|
|
81281
|
-
openCallout(
|
|
81332
|
+
openCallout(boundMessage(goingNext), {
|
|
81282
81333
|
anchorElement: middleRef.current,
|
|
81283
81334
|
status: "info",
|
|
81284
81335
|
openingEvent: e.detail.event ?? e
|
|
@@ -81337,12 +81388,14 @@ const Spin = ({
|
|
|
81337
81388
|
const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
|
|
81338
81389
|
const wayOut = atStart => {
|
|
81339
81390
|
const isNext = atStart ? startIsNext : !startIsNext;
|
|
81391
|
+
const allowed = atStart ? startAllowed : endAllowed;
|
|
81340
81392
|
return jsx(WayOut, {
|
|
81341
81393
|
atStart: atStart,
|
|
81342
81394
|
onPointerDown: e => {
|
|
81343
81395
|
wayOutPointerTypeRef.current = e.pointerType;
|
|
81344
81396
|
},
|
|
81345
|
-
|
|
81397
|
+
unavailable: !allowed || loadingResolved || readOnlyResolved,
|
|
81398
|
+
unavailableMessage: () => wayOutMessage(allowed, isNext),
|
|
81346
81399
|
label: isNext ? nextLabel ?? naviI18n("spin.next") : previousLabel ?? naviI18n("spin.previous"),
|
|
81347
81400
|
onPress: e => {
|
|
81348
81401
|
focusMiddle();
|
|
@@ -81389,6 +81442,8 @@ const Spin = ({
|
|
|
81389
81442
|
...valueProps,
|
|
81390
81443
|
min: min,
|
|
81391
81444
|
max: max,
|
|
81445
|
+
minMessage: minMessage,
|
|
81446
|
+
maxMessage: maxMessage,
|
|
81392
81447
|
step: step,
|
|
81393
81448
|
readOnly: readOnly,
|
|
81394
81449
|
disabled: disabled,
|
|
@@ -81422,6 +81477,8 @@ const Spin = ({
|
|
|
81422
81477
|
...valueProps,
|
|
81423
81478
|
min: min,
|
|
81424
81479
|
max: max,
|
|
81480
|
+
minMessage: minMessage,
|
|
81481
|
+
maxMessage: maxMessage,
|
|
81425
81482
|
readOnly: readOnly,
|
|
81426
81483
|
disabled: disabled,
|
|
81427
81484
|
loading: loading,
|
|
@@ -81532,6 +81589,7 @@ const Spin = ({
|
|
|
81532
81589
|
const WayOut = ({
|
|
81533
81590
|
atStart,
|
|
81534
81591
|
commandFor,
|
|
81592
|
+
unavailable,
|
|
81535
81593
|
unavailableMessage,
|
|
81536
81594
|
label,
|
|
81537
81595
|
onPress,
|
|
@@ -81542,10 +81600,10 @@ const WayOut = ({
|
|
|
81542
81600
|
// onPointerUp below).
|
|
81543
81601
|
const pointerDownRef = useRef(null);
|
|
81544
81602
|
const press = e => {
|
|
81545
|
-
if (
|
|
81603
|
+
if (unavailable) {
|
|
81546
81604
|
// Why it does nothing, said where one pressed: a control would have
|
|
81547
81605
|
// done this through its own interaction gate, and this one has none.
|
|
81548
|
-
openCallout(unavailableMessage, {
|
|
81606
|
+
openCallout(unavailableMessage(), {
|
|
81549
81607
|
anchorElement: e.currentTarget,
|
|
81550
81608
|
status: "info",
|
|
81551
81609
|
openingEvent: e
|
|
@@ -81572,8 +81630,8 @@ const WayOut = ({
|
|
|
81572
81630
|
,
|
|
81573
81631
|
role: "button",
|
|
81574
81632
|
"aria-label": label,
|
|
81575
|
-
"aria-disabled":
|
|
81576
|
-
"data-unavailable":
|
|
81633
|
+
"aria-disabled": unavailable ? "true" : undefined,
|
|
81634
|
+
"data-unavailable": unavailable ? "" : undefined
|
|
81577
81635
|
// At the chevron, not beside it: a callout aims its arrow at where the
|
|
81578
81636
|
// anchor's text starts, and there is no text here — only a glyph in the
|
|
81579
81637
|
// middle of the box, which is what one pressed and what the answer is
|
|
@@ -81806,6 +81864,8 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
81806
81864
|
* defaultValue?: number|string,
|
|
81807
81865
|
* min?: number,
|
|
81808
81866
|
* max?: number,
|
|
81867
|
+
* minMessage?: any,
|
|
81868
|
+
* maxMessage?: any,
|
|
81809
81869
|
* step?: number,
|
|
81810
81870
|
* pad?: number,
|
|
81811
81871
|
* loop?: boolean,
|
|
@@ -81911,6 +81971,8 @@ const numberAtStep = (value, count, {
|
|
|
81911
81971
|
* name?: string,
|
|
81912
81972
|
* min?: string,
|
|
81913
81973
|
* max?: string,
|
|
81974
|
+
* minMessage?: any,
|
|
81975
|
+
* maxMessage?: any,
|
|
81914
81976
|
* step?: number,
|
|
81915
81977
|
* lang?: string,
|
|
81916
81978
|
* renderDay?: (day: string) => import("ignore:preact").ComponentChildren,
|