@jsenv/navi 0.29.434 → 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 +291 -150
- package/dist/dev/jsenv_navi.js.map +13 -12
- package/dist/jsenv_navi.js +231 -138
- package/dist/jsenv_navi.js.map +13 -12
- 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
|
*
|
|
@@ -12938,15 +12958,12 @@ const useNavStateWithWarnings = (id, options) => {
|
|
|
12938
12958
|
ownerRef.current = {};
|
|
12939
12959
|
}
|
|
12940
12960
|
const owner = ownerRef.current;
|
|
12941
|
-
// Claimed during render so warnAboutOrphanGeneratedKeys sees every id of
|
|
12942
|
-
// the commit, including the ones whose effect has not run yet. The conflict
|
|
12943
|
-
// is judged in the effect: when a screen replaces another in one commit,
|
|
12944
|
-
// preact renders the new children before unmounting the old ones, so at
|
|
12945
|
-
// render time the id may still be held by a component about to go.
|
|
12946
|
-
if (id !== undefined && !idUsageMap.has(id)) {
|
|
12947
|
-
idUsageMap.set(id, { owner, stackTrace: new Error().stack });
|
|
12948
|
-
}
|
|
12949
12961
|
|
|
12962
|
+
// Claimed in the effect, never during render: only the effect has a cleanup
|
|
12963
|
+
// to give the id back, and a render is not always followed by its effects.
|
|
12964
|
+
// A page parked by a Suspense boundary (every hook cleanup run), rendered
|
|
12965
|
+
// again and taken down before the paint, leaves a render-time claim holding
|
|
12966
|
+
// the id for nobody — and the next mount reads a conflict.
|
|
12950
12967
|
useEffect(() => {
|
|
12951
12968
|
if (id === undefined) {
|
|
12952
12969
|
return undefined;
|
|
@@ -12960,12 +12977,9 @@ This can cause UI state conflicts and unexpected behavior.
|
|
|
12960
12977
|
Consider using unique IDs for each component instance.`,
|
|
12961
12978
|
);
|
|
12962
12979
|
} else {
|
|
12963
|
-
// Also set when absent: preact/compat's Suspense parks a subtree by
|
|
12964
|
-
// running every hook cleanup in it, and the render resuming it carries
|
|
12965
|
-
// the same id.
|
|
12966
12980
|
idUsageMap.set(id, { owner, stackTrace: new Error().stack });
|
|
12967
12981
|
}
|
|
12968
|
-
|
|
12982
|
+
scheduleOrphanGeneratedKeysCheck();
|
|
12969
12983
|
return () => {
|
|
12970
12984
|
if (idUsageMap.get(id)?.owner === owner) {
|
|
12971
12985
|
idUsageMap.delete(id);
|
|
@@ -12981,6 +12995,20 @@ Consider using unique IDs for each component instance.`,
|
|
|
12981
12995
|
// and the mount coming back to the entry generates another id: the state is
|
|
12982
12996
|
// there, and nothing reads it. Reported when a component mounting on the entry
|
|
12983
12997
|
// finds such a key, the moment someone expected the state back.
|
|
12998
|
+
//
|
|
12999
|
+
// Checked once every effect of the flush has claimed its id: a component
|
|
13000
|
+
// whose effect comes later in the same flush is not an orphan.
|
|
13001
|
+
let orphanGeneratedKeysCheckScheduled = false;
|
|
13002
|
+
const scheduleOrphanGeneratedKeysCheck = () => {
|
|
13003
|
+
if (orphanGeneratedKeysCheckScheduled) {
|
|
13004
|
+
return;
|
|
13005
|
+
}
|
|
13006
|
+
orphanGeneratedKeysCheckScheduled = true;
|
|
13007
|
+
queueMicrotask(() => {
|
|
13008
|
+
orphanGeneratedKeysCheckScheduled = false;
|
|
13009
|
+
warnAboutOrphanGeneratedKeys();
|
|
13010
|
+
});
|
|
13011
|
+
};
|
|
12984
13012
|
const warnAboutOrphanGeneratedKeys = () => {
|
|
12985
13013
|
const state = browserIntegration.getDocumentState();
|
|
12986
13014
|
if (!state) {
|
|
@@ -13003,6 +13031,7 @@ const warnAboutOrphanGeneratedKeys = () => {
|
|
|
13003
13031
|
|
|
13004
13032
|
const NO_OP = () => {};
|
|
13005
13033
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
13034
|
+
const countRenders$1 = (count) => count + 1;
|
|
13006
13035
|
// What the computed below answers for a key the document state does not hold:
|
|
13007
13036
|
// key presence is the flag, and the value under a present key may be anything,
|
|
13008
13037
|
// undefined included.
|
|
@@ -13031,7 +13060,36 @@ const useNavStateBasic = (
|
|
|
13031
13060
|
[id],
|
|
13032
13061
|
);
|
|
13033
13062
|
const keyValue = keyValueComputed.value;
|
|
13034
|
-
|
|
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;
|
|
13035
13093
|
const onLeaveRef = useRef(onLeave);
|
|
13036
13094
|
onLeaveRef.current = onLeave;
|
|
13037
13095
|
const prevKeyInStateRef = useRef(keyInState);
|
|
@@ -13066,7 +13124,7 @@ const useNavStateBasic = (
|
|
|
13066
13124
|
effectiveType = "replace";
|
|
13067
13125
|
}
|
|
13068
13126
|
|
|
13069
|
-
const currentValue = keyInState ?
|
|
13127
|
+
const currentValue = keyInState ? keyValueRead : defaultValue;
|
|
13070
13128
|
|
|
13071
13129
|
if (debug) {
|
|
13072
13130
|
console.debug(`useNavState(${id}) current value is ${currentValue}`);
|
|
@@ -13134,6 +13192,8 @@ const useNavStateBasic = (
|
|
|
13134
13192
|
/**
|
|
13135
13193
|
* Stores a named value in the browser's document state and returns it reactively.
|
|
13136
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.
|
|
13137
13197
|
*
|
|
13138
13198
|
* @param {string} id
|
|
13139
13199
|
* Unique key used to store the value in document state. Must be stable across renders.
|
|
@@ -20285,18 +20345,17 @@ const MIN_CONSTRAINT = {
|
|
|
20285
20345
|
if (cmp === null || cmp >= 0) {
|
|
20286
20346
|
return null;
|
|
20287
20347
|
}
|
|
20288
|
-
|
|
20289
|
-
return naviI18n("constraint.min.duration.default", {
|
|
20290
|
-
min: formatDuration(min),
|
|
20291
|
-
});
|
|
20348
|
+
return boundMessage(field, "min");
|
|
20292
20349
|
}
|
|
20293
20350
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
20294
20351
|
return null;
|
|
20295
20352
|
}
|
|
20296
|
-
const
|
|
20297
|
-
|
|
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 === "") {
|
|
20298
20356
|
return null;
|
|
20299
20357
|
}
|
|
20358
|
+
const minString = String(min);
|
|
20300
20359
|
const type = field.controlHostProps.type;
|
|
20301
20360
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20302
20361
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -20312,40 +20371,16 @@ const MIN_CONSTRAINT = {
|
|
|
20312
20371
|
if (isNaN(numericValue)) {
|
|
20313
20372
|
return null;
|
|
20314
20373
|
}
|
|
20315
|
-
if (numericValue
|
|
20316
|
-
|
|
20317
|
-
return naviI18n(`constraint.min.hour.default`, {
|
|
20318
|
-
min: minString,
|
|
20319
|
-
});
|
|
20320
|
-
}
|
|
20321
|
-
if (naviInputType === "minute") {
|
|
20322
|
-
return naviI18n(`constraint.min.minute.default`, {
|
|
20323
|
-
min: minString,
|
|
20324
|
-
});
|
|
20325
|
-
}
|
|
20326
|
-
if (naviInputType === "second") {
|
|
20327
|
-
return naviI18n(`constraint.min.second.default`, {
|
|
20328
|
-
min: minString,
|
|
20329
|
-
});
|
|
20330
|
-
}
|
|
20331
|
-
if (naviInputType === "percentage") {
|
|
20332
|
-
return naviI18n(`constraint.min.percentage.default`, {
|
|
20333
|
-
min: minString,
|
|
20334
|
-
});
|
|
20335
|
-
}
|
|
20336
|
-
return naviI18n(`constraint.min.number.default`, {
|
|
20337
|
-
min: minString,
|
|
20338
|
-
});
|
|
20374
|
+
if (numericValue >= minNumber) {
|
|
20375
|
+
return null;
|
|
20339
20376
|
}
|
|
20340
|
-
return
|
|
20377
|
+
return boundMessage(field, "min");
|
|
20341
20378
|
}
|
|
20342
20379
|
if (type === "time") {
|
|
20343
20380
|
const [minHours, minMinutes] = minString.split(":").map(Number);
|
|
20344
20381
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
20345
20382
|
if (hours < minHours || (hours === minHours && minutes < minMinutes)) {
|
|
20346
|
-
return
|
|
20347
|
-
min: minString,
|
|
20348
|
-
});
|
|
20383
|
+
return boundMessage(field, "min");
|
|
20349
20384
|
}
|
|
20350
20385
|
return null;
|
|
20351
20386
|
}
|
|
@@ -20353,18 +20388,13 @@ const MIN_CONSTRAINT = {
|
|
|
20353
20388
|
// so they never need a min/max validation message.
|
|
20354
20389
|
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
20355
20390
|
if (valueAsString < minString) {
|
|
20356
|
-
|
|
20357
|
-
if (minString === todayIso) {
|
|
20358
|
-
return naviI18n("constraint.min.date.today.default");
|
|
20359
|
-
}
|
|
20360
|
-
return naviI18n("constraint.min.date.default", {
|
|
20361
|
-
min: formatDateIso(minString, type),
|
|
20362
|
-
});
|
|
20391
|
+
return boundMessage(field, "min");
|
|
20363
20392
|
}
|
|
20364
20393
|
return null;
|
|
20365
20394
|
}
|
|
20366
20395
|
return null;
|
|
20367
20396
|
},
|
|
20397
|
+
messageAtBound: (field) => boundMessage(field, "min"),
|
|
20368
20398
|
};
|
|
20369
20399
|
CONSTRAINT_ATTRIBUTE_SET.add("min");
|
|
20370
20400
|
|
|
@@ -20384,18 +20414,17 @@ const MAX_CONSTRAINT = {
|
|
|
20384
20414
|
if (cmp === null || cmp <= 0) {
|
|
20385
20415
|
return null;
|
|
20386
20416
|
}
|
|
20387
|
-
|
|
20388
|
-
return naviI18n("constraint.max.duration.default", {
|
|
20389
|
-
max: formatDuration(max),
|
|
20390
|
-
});
|
|
20417
|
+
return boundMessage(field, "max");
|
|
20391
20418
|
}
|
|
20392
20419
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
20393
20420
|
return null;
|
|
20394
20421
|
}
|
|
20395
|
-
const
|
|
20396
|
-
|
|
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 === "") {
|
|
20397
20425
|
return null;
|
|
20398
20426
|
}
|
|
20427
|
+
const maxString = String(max);
|
|
20399
20428
|
const type = field.controlHostProps.type;
|
|
20400
20429
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20401
20430
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -20414,38 +20443,13 @@ const MAX_CONSTRAINT = {
|
|
|
20414
20443
|
if (numericValue <= maxNumber) {
|
|
20415
20444
|
return null;
|
|
20416
20445
|
}
|
|
20417
|
-
|
|
20418
|
-
if (naviInputType === "hour") {
|
|
20419
|
-
return naviI18n(`constraint.max.hour.default`, {
|
|
20420
|
-
max: maxString,
|
|
20421
|
-
});
|
|
20422
|
-
}
|
|
20423
|
-
if (naviInputType === "minute") {
|
|
20424
|
-
return naviI18n(`constraint.max.minute.default`, {
|
|
20425
|
-
max: maxString,
|
|
20426
|
-
});
|
|
20427
|
-
}
|
|
20428
|
-
if (naviInputType === "second") {
|
|
20429
|
-
return naviI18n(`constraint.max.second.default`, {
|
|
20430
|
-
max: maxString,
|
|
20431
|
-
});
|
|
20432
|
-
}
|
|
20433
|
-
if (naviInputType === "percentage") {
|
|
20434
|
-
return naviI18n(`constraint.max.percentage.default`, {
|
|
20435
|
-
max: maxString,
|
|
20436
|
-
});
|
|
20437
|
-
}
|
|
20438
|
-
return naviI18n(`constraint.max.number.default`, {
|
|
20439
|
-
max: maxString,
|
|
20440
|
-
});
|
|
20446
|
+
return boundMessage(field, "max");
|
|
20441
20447
|
}
|
|
20442
20448
|
if (type === "time") {
|
|
20443
20449
|
const [maxHours, maxMinutes] = maxString.split(":").map(Number);
|
|
20444
20450
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
20445
20451
|
if (hours > maxHours || (hours === maxHours && minutes > maxMinutes)) {
|
|
20446
|
-
return
|
|
20447
|
-
max: maxString,
|
|
20448
|
-
});
|
|
20452
|
+
return boundMessage(field, "max");
|
|
20449
20453
|
}
|
|
20450
20454
|
return null;
|
|
20451
20455
|
}
|
|
@@ -20453,17 +20457,11 @@ const MAX_CONSTRAINT = {
|
|
|
20453
20457
|
if (valueAsString <= maxString) {
|
|
20454
20458
|
return null;
|
|
20455
20459
|
}
|
|
20456
|
-
|
|
20457
|
-
const todayIso = getTodayIso(type);
|
|
20458
|
-
if (maxString === todayIso) {
|
|
20459
|
-
return naviI18n("constraint.max.date.today.default");
|
|
20460
|
-
}
|
|
20461
|
-
return naviI18n("constraint.max.date.default", {
|
|
20462
|
-
max: formatDateIso(maxString, type),
|
|
20463
|
-
});
|
|
20460
|
+
return boundMessage(field, "max");
|
|
20464
20461
|
}
|
|
20465
20462
|
return null;
|
|
20466
20463
|
},
|
|
20464
|
+
messageAtBound: (field) => boundMessage(field, "max"),
|
|
20467
20465
|
};
|
|
20468
20466
|
CONSTRAINT_ATTRIBUTE_SET.add("max");
|
|
20469
20467
|
|
|
@@ -20658,6 +20656,51 @@ const formatMsToTime = (ms, showSeconds) => {
|
|
|
20658
20656
|
return `${hh}:${mm}:${ss}`;
|
|
20659
20657
|
};
|
|
20660
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
|
+
|
|
20661
20704
|
const getTodayIso = (inputType) => {
|
|
20662
20705
|
const now = new Date();
|
|
20663
20706
|
const yyyy = now.getFullYear();
|
|
@@ -29150,24 +29193,6 @@ const TYPE_CONVERTERS = {
|
|
|
29150
29193
|
},
|
|
29151
29194
|
};
|
|
29152
29195
|
|
|
29153
|
-
/**
|
|
29154
|
-
* What a component can learn about the page it is rendered in, from the route
|
|
29155
|
-
* container rendering that page (see useContentKeepingLeavingPages in
|
|
29156
|
-
* route.jsx). `null` outside of any route container.
|
|
29157
|
-
*
|
|
29158
|
-
* - `isShown()` — whether the container would still show the page. False as
|
|
29159
|
-
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
29160
|
-
* hidden until the route transition leaving it is over, and nothing it reads
|
|
29161
|
-
* is worth a render any more. False for good once it is kept while leaving: a
|
|
29162
|
-
* return to its route mounts the page anew. Asked outside any render — by a
|
|
29163
|
-
* subscription deciding whether to re-render — and it reads the routes
|
|
29164
|
-
* untracked, so asking subscribes nobody to them.
|
|
29165
|
-
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
29166
|
-
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
29167
|
-
* bar) steps out rather than showing with the page arriving.
|
|
29168
|
-
*/
|
|
29169
|
-
const RoutePageContext = createContext(null);
|
|
29170
|
-
|
|
29171
29196
|
const promiseStateWeakMap = new WeakMap();
|
|
29172
29197
|
const usePromiseAsyncData = (
|
|
29173
29198
|
promise,
|
|
@@ -34068,11 +34093,17 @@ const useContentKeepingLeavingPages = (branch, readShownBranch, parentPage) => {
|
|
|
34068
34093
|
}
|
|
34069
34094
|
}
|
|
34070
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.
|
|
34071
34101
|
useLayoutEffect(() => {
|
|
34072
34102
|
return () => {
|
|
34073
34103
|
for (const entry of leavingRef.current) {
|
|
34074
34104
|
entry.hold.containers.delete(rerender);
|
|
34075
34105
|
}
|
|
34106
|
+
leavingRef.current = [];
|
|
34076
34107
|
};
|
|
34077
34108
|
}, []);
|
|
34078
34109
|
if (leaving.length === 0) {
|
|
@@ -54662,6 +54693,26 @@ const Expandable = props => {
|
|
|
54662
54693
|
return stopRide;
|
|
54663
54694
|
};
|
|
54664
54695
|
|
|
54696
|
+
// A silent change is a state the page was already in (see openEffect): it
|
|
54697
|
+
// lands at once. Not playing a reveal is not enough — the stylesheet's own
|
|
54698
|
+
// transition plays it whenever the previous state's style was resolved
|
|
54699
|
+
// before the render: an expandable mounted closed and measured in the same
|
|
54700
|
+
// commit (a List sizing its items) before its deferred mount-time open, or
|
|
54701
|
+
// one kept on screen across the page change.
|
|
54702
|
+
const renderWithoutMovement = (contentContainer, render) => {
|
|
54703
|
+
if (!animation || !contentContainer) {
|
|
54704
|
+
flushSyncRendering(render);
|
|
54705
|
+
return;
|
|
54706
|
+
}
|
|
54707
|
+
contentContainer.style.transitionProperty = "none";
|
|
54708
|
+
flushSyncRendering(render);
|
|
54709
|
+
// The new style must be resolved while transitions are off — re-enabled
|
|
54710
|
+
// in the same recalc, the change would transition after all.
|
|
54711
|
+
// eslint-disable-next-line no-unused-expressions
|
|
54712
|
+
getComputedStyle(contentContainer).transitionProperty;
|
|
54713
|
+
contentContainer.style.transitionProperty = "";
|
|
54714
|
+
};
|
|
54715
|
+
|
|
54665
54716
|
// What opening LOOKS like here, and how to undo it — the one thing an
|
|
54666
54717
|
// expandable owns that a popup does not (see open_controller.js). Reassigned
|
|
54667
54718
|
// on every render so it always closes over the latest props.
|
|
@@ -54682,10 +54733,15 @@ const Expandable = props => {
|
|
|
54682
54733
|
// reopening during a collapse — read while the DOM still says closed.
|
|
54683
54734
|
const startRect = revealing ? contentContainer.getBoundingClientRect() : null;
|
|
54684
54735
|
cancelSettleWatch();
|
|
54685
|
-
|
|
54736
|
+
const renderOpened = () => {
|
|
54686
54737
|
setOpened(true);
|
|
54687
54738
|
setSettled(!revealing);
|
|
54688
|
-
}
|
|
54739
|
+
};
|
|
54740
|
+
if (silent) {
|
|
54741
|
+
renderWithoutMovement(contentContainer, renderOpened);
|
|
54742
|
+
} else {
|
|
54743
|
+
flushSyncRendering(renderOpened);
|
|
54744
|
+
}
|
|
54689
54745
|
if (hasAction) {
|
|
54690
54746
|
runUnwatched(() => effectiveAction.run());
|
|
54691
54747
|
}
|
|
@@ -54724,16 +54780,22 @@ const Expandable = props => {
|
|
|
54724
54780
|
cancelSettleWatch();
|
|
54725
54781
|
// `silent`: closed by the navigation that puts another page on screen,
|
|
54726
54782
|
// which is its movement (see page_change.js).
|
|
54727
|
-
const
|
|
54783
|
+
const silentClose = Boolean(closeEvent.detail.silent);
|
|
54784
|
+
const collapsing = animation && !silentClose && Boolean(contentContainerAtClose);
|
|
54728
54785
|
if (collapsing) {
|
|
54729
54786
|
// Now, while the content is still fully laid out — the collapsing
|
|
54730
54787
|
// track uncovers a content frozen at that size.
|
|
54731
54788
|
freezeContentSize();
|
|
54732
54789
|
}
|
|
54733
|
-
|
|
54790
|
+
const renderClosed = () => {
|
|
54734
54791
|
setOpened(false);
|
|
54735
54792
|
setSettled(!collapsing);
|
|
54736
|
-
}
|
|
54793
|
+
};
|
|
54794
|
+
if (silentClose) {
|
|
54795
|
+
renderWithoutMovement(contentContainerAtClose, renderClosed);
|
|
54796
|
+
} else {
|
|
54797
|
+
flushSyncRendering(renderClosed);
|
|
54798
|
+
}
|
|
54737
54799
|
if (restoreFocus) {
|
|
54738
54800
|
restoreFocus(closeEvent);
|
|
54739
54801
|
} else if (focusedBeforeClose && contentContainerAtClose && contentContainerAtClose.contains(focusedBeforeClose)) {
|
|
@@ -74651,6 +74713,45 @@ const LIST_STYLE_CSS_VARS = {
|
|
|
74651
74713
|
backgroundColor: "--list-background-color"
|
|
74652
74714
|
};
|
|
74653
74715
|
const LIST_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":focus-within", ":read-only", ":disabled", ":-navi-void", ":-navi-expanded"];
|
|
74716
|
+
// Two lists mounted at once under one `id` are one entry to the scroll memory:
|
|
74717
|
+
// each overwrites the other's position (see docs/scroll.md). Said the moment
|
|
74718
|
+
// the second one mounts, once per id — on a route transition, that is the page
|
|
74719
|
+
// arriving while the page being left is still mounted, hidden.
|
|
74720
|
+
const createSharedRememberedIdWarning = () => {
|
|
74721
|
+
const mountedListsById = new Map();
|
|
74722
|
+
const warnedIdSet = new Set();
|
|
74723
|
+
return (listId, rememberScroll, ref) => {
|
|
74724
|
+
useLayoutEffect(() => {
|
|
74725
|
+
if (!rememberScroll) {
|
|
74726
|
+
return undefined;
|
|
74727
|
+
}
|
|
74728
|
+
let mountedLists = mountedListsById.get(listId);
|
|
74729
|
+
if (!mountedLists) {
|
|
74730
|
+
mountedLists = new Set();
|
|
74731
|
+
mountedListsById.set(listId, mountedLists);
|
|
74732
|
+
}
|
|
74733
|
+
const mounted = {
|
|
74734
|
+
element: ref.current
|
|
74735
|
+
};
|
|
74736
|
+
if (mountedLists.size > 0 && !warnedIdSet.has(listId)) {
|
|
74737
|
+
warnedIdSet.add(listId);
|
|
74738
|
+
const [other] = mountedLists;
|
|
74739
|
+
console.warn(`<List id="${listId}"> mounted while another list with this id is mounted. The position a list comes back to is kept under its id, so the two overwrite each other's: the one arriving can open where the other one was, or come back at its top. Lists are mounted together on one page, under a popup, in a bar every page keeps, and during a route transition, whose page being left stays mounted until the movement ends. Give each list an id naming it in the app (a component drawing a list on several pages takes its id from the page), or set scrollResetOnNavigation on a list with no use for coming back. See docs/scroll.md.`, {
|
|
74740
|
+
list: mounted.element,
|
|
74741
|
+
otherList: other.element
|
|
74742
|
+
});
|
|
74743
|
+
}
|
|
74744
|
+
mountedLists.add(mounted);
|
|
74745
|
+
return () => {
|
|
74746
|
+
mountedLists.delete(mounted);
|
|
74747
|
+
if (mountedLists.size === 0) {
|
|
74748
|
+
mountedListsById.delete(listId);
|
|
74749
|
+
}
|
|
74750
|
+
};
|
|
74751
|
+
}, [listId, rememberScroll]);
|
|
74752
|
+
};
|
|
74753
|
+
};
|
|
74754
|
+
const useWarnSharedRememberedId = createSharedRememberedIdWarning() ;
|
|
74654
74755
|
const useListScrollSync = ({
|
|
74655
74756
|
ref,
|
|
74656
74757
|
listItems,
|
|
@@ -75557,6 +75658,7 @@ const useListScrollSync = ({
|
|
|
75557
75658
|
}
|
|
75558
75659
|
};
|
|
75559
75660
|
}, []);
|
|
75661
|
+
useWarnSharedRememberedId(listId, rememberScroll, ref);
|
|
75560
75662
|
|
|
75561
75663
|
// A list that gets narrower rewraps every item it holds, so everything below
|
|
75562
75664
|
// moves and the reader loses their place — the very thing scrolling a long
|
|
@@ -83093,6 +83195,8 @@ const TRAVELS_NONE = [];
|
|
|
83093
83195
|
* name?: string,
|
|
83094
83196
|
* min?: any,
|
|
83095
83197
|
* max?: any,
|
|
83198
|
+
* minMessage?: any,
|
|
83199
|
+
* maxMessage?: any,
|
|
83096
83200
|
* step?: number,
|
|
83097
83201
|
* type?: string,
|
|
83098
83202
|
* editable?: boolean,
|
|
@@ -83131,7 +83235,15 @@ const TRAVELS_NONE = [];
|
|
|
83131
83235
|
* @param {number} [step=1] How many steps a press covers.
|
|
83132
83236
|
* @param {number} [duration=250] How long a travel takes, in milliseconds.
|
|
83133
83237
|
* @param {any} [min] The first value one can reach; `max` is the last. Beyond
|
|
83134
|
-
* 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`.
|
|
83135
83247
|
* @param {string} [padding] The room around the value, `paddingX`/`paddingY`
|
|
83136
83248
|
* and the four sides included. It goes on what is inside the box rather than
|
|
83137
83249
|
* on the box: above and below it is taken by the value AND by the two
|
|
@@ -83161,6 +83273,8 @@ const Spin = ({
|
|
|
83161
83273
|
name,
|
|
83162
83274
|
min,
|
|
83163
83275
|
max,
|
|
83276
|
+
minMessage,
|
|
83277
|
+
maxMessage,
|
|
83164
83278
|
step = 1,
|
|
83165
83279
|
duration = 250,
|
|
83166
83280
|
type = "text",
|
|
@@ -83286,15 +83400,31 @@ const Spin = ({
|
|
|
83286
83400
|
const readOnlyResolved = Boolean(readOnly || readOnlyFromAbove || loadingResolved);
|
|
83287
83401
|
const disabledResolved = Boolean(disabled || disabledFromAbove);
|
|
83288
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
|
+
|
|
83289
83419
|
// Why a chevron refuses, in its own words — and only when the reason is
|
|
83290
83420
|
// ours: the end of what one may reach is something this control knows and
|
|
83291
83421
|
// navi cannot guess. For everything else the reason belongs to the state the
|
|
83292
83422
|
// whole control is in, and it is said about the control ("read-only",
|
|
83293
83423
|
// "busy") rather than about the button, which is not what the user was
|
|
83294
83424
|
// pressing.
|
|
83295
|
-
const wayOutMessage = (allowed,
|
|
83425
|
+
const wayOutMessage = (allowed, isNext) => {
|
|
83296
83426
|
if (!allowed) {
|
|
83297
|
-
return
|
|
83427
|
+
return boundMessage(isNext);
|
|
83298
83428
|
}
|
|
83299
83429
|
if (loadingResolved) {
|
|
83300
83430
|
return naviI18n("constraint.busy.default");
|
|
@@ -83406,7 +83536,7 @@ const Spin = ({
|
|
|
83406
83536
|
// answer belongs.
|
|
83407
83537
|
e.preventDefault();
|
|
83408
83538
|
e.stopPropagation();
|
|
83409
|
-
openCallout(
|
|
83539
|
+
openCallout(boundMessage(goingNext), {
|
|
83410
83540
|
anchorElement: middleRef.current,
|
|
83411
83541
|
status: "info",
|
|
83412
83542
|
openingEvent: e.detail.event ?? e
|
|
@@ -83465,12 +83595,14 @@ const Spin = ({
|
|
|
83465
83595
|
const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
|
|
83466
83596
|
const wayOut = atStart => {
|
|
83467
83597
|
const isNext = atStart ? startIsNext : !startIsNext;
|
|
83598
|
+
const allowed = atStart ? startAllowed : endAllowed;
|
|
83468
83599
|
return jsx(WayOut, {
|
|
83469
83600
|
atStart: atStart,
|
|
83470
83601
|
onPointerDown: e => {
|
|
83471
83602
|
wayOutPointerTypeRef.current = e.pointerType;
|
|
83472
83603
|
},
|
|
83473
|
-
|
|
83604
|
+
unavailable: !allowed || loadingResolved || readOnlyResolved,
|
|
83605
|
+
unavailableMessage: () => wayOutMessage(allowed, isNext),
|
|
83474
83606
|
label: isNext ? nextLabel ?? naviI18n("spin.next") : previousLabel ?? naviI18n("spin.previous"),
|
|
83475
83607
|
onPress: e => {
|
|
83476
83608
|
focusMiddle();
|
|
@@ -83517,6 +83649,8 @@ const Spin = ({
|
|
|
83517
83649
|
...valueProps,
|
|
83518
83650
|
min: min,
|
|
83519
83651
|
max: max,
|
|
83652
|
+
minMessage: minMessage,
|
|
83653
|
+
maxMessage: maxMessage,
|
|
83520
83654
|
step: step,
|
|
83521
83655
|
readOnly: readOnly,
|
|
83522
83656
|
disabled: disabled,
|
|
@@ -83550,6 +83684,8 @@ const Spin = ({
|
|
|
83550
83684
|
...valueProps,
|
|
83551
83685
|
min: min,
|
|
83552
83686
|
max: max,
|
|
83687
|
+
minMessage: minMessage,
|
|
83688
|
+
maxMessage: maxMessage,
|
|
83553
83689
|
readOnly: readOnly,
|
|
83554
83690
|
disabled: disabled,
|
|
83555
83691
|
loading: loading,
|
|
@@ -83660,6 +83796,7 @@ const Spin = ({
|
|
|
83660
83796
|
const WayOut = ({
|
|
83661
83797
|
atStart,
|
|
83662
83798
|
commandFor,
|
|
83799
|
+
unavailable,
|
|
83663
83800
|
unavailableMessage,
|
|
83664
83801
|
label,
|
|
83665
83802
|
onPress,
|
|
@@ -83670,10 +83807,10 @@ const WayOut = ({
|
|
|
83670
83807
|
// onPointerUp below).
|
|
83671
83808
|
const pointerDownRef = useRef(null);
|
|
83672
83809
|
const press = e => {
|
|
83673
|
-
if (
|
|
83810
|
+
if (unavailable) {
|
|
83674
83811
|
// Why it does nothing, said where one pressed: a control would have
|
|
83675
83812
|
// done this through its own interaction gate, and this one has none.
|
|
83676
|
-
openCallout(unavailableMessage, {
|
|
83813
|
+
openCallout(unavailableMessage(), {
|
|
83677
83814
|
anchorElement: e.currentTarget,
|
|
83678
83815
|
status: "info",
|
|
83679
83816
|
openingEvent: e
|
|
@@ -83700,8 +83837,8 @@ const WayOut = ({
|
|
|
83700
83837
|
,
|
|
83701
83838
|
role: "button",
|
|
83702
83839
|
"aria-label": label,
|
|
83703
|
-
"aria-disabled":
|
|
83704
|
-
"data-unavailable":
|
|
83840
|
+
"aria-disabled": unavailable ? "true" : undefined,
|
|
83841
|
+
"data-unavailable": unavailable ? "" : undefined
|
|
83705
83842
|
// At the chevron, not beside it: a callout aims its arrow at where the
|
|
83706
83843
|
// anchor's text starts, and there is no text here — only a glyph in the
|
|
83707
83844
|
// middle of the box, which is what one pressed and what the answer is
|
|
@@ -83934,6 +84071,8 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
83934
84071
|
* defaultValue?: number|string,
|
|
83935
84072
|
* min?: number,
|
|
83936
84073
|
* max?: number,
|
|
84074
|
+
* minMessage?: any,
|
|
84075
|
+
* maxMessage?: any,
|
|
83937
84076
|
* step?: number,
|
|
83938
84077
|
* pad?: number,
|
|
83939
84078
|
* loop?: boolean,
|
|
@@ -84039,6 +84178,8 @@ const numberAtStep = (value, count, {
|
|
|
84039
84178
|
* name?: string,
|
|
84040
84179
|
* min?: string,
|
|
84041
84180
|
* max?: string,
|
|
84181
|
+
* minMessage?: any,
|
|
84182
|
+
* maxMessage?: any,
|
|
84042
84183
|
* step?: number,
|
|
84043
84184
|
* lang?: string,
|
|
84044
84185
|
* renderDay?: (day: string) => import("ignore:preact").ComponentChildren,
|