@jsenv/navi 0.29.435 → 0.29.437
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 +1015 -250
- package/dist/dev/jsenv_navi.js.map +25 -20
- package/dist/jsenv_navi.js +1015 -250
- package/dist/jsenv_navi.js.map +25 -20
- package/docs/AI_INSTRUCTIONS.md +3 -2
- package/docs/data_states.md +3 -1
- package/docs/list_refresh.md +4 -1
- package/docs/network_policy.md +9 -4
- package/docs/resource.md +93 -1
- package/docs/route_transitions.md +7 -0
- package/docs/scroll.md +3 -1
- 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",
|
|
@@ -3694,8 +3696,11 @@ const actionWeakMap = new WeakMap();
|
|
|
3694
3696
|
* than a first load (data_states.md). Called at the start of every run whose
|
|
3695
3697
|
* value is undefined; the answer replaces it as usual, and `undefined` means
|
|
3696
3698
|
* "nothing known". A resource `GET` uses it to draw the row its store holds.
|
|
3697
|
-
*
|
|
3698
|
-
*
|
|
3699
|
+
* @param {boolean} [rootOptions.inheritData=true] - `false`: a binding moving
|
|
3700
|
+
* to new params starts the new instance without the previous params' answer
|
|
3701
|
+
* (see createActionProxyFromSignal). For an answer that would say something
|
|
3702
|
+
* false about the new params (a `GET` drawing another row), not for one that
|
|
3703
|
+
* stands in while the next one comes (a list of search results).
|
|
3699
3704
|
* @param {{ ms?: number, max?: number }} [rootOptions.keep] - how long an
|
|
3700
3705
|
* answer stays good once it has landed. Without it an answer lives exactly as
|
|
3701
3706
|
* long as something references it: the screen that asked goes away, and
|
|
@@ -3738,6 +3743,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3738
3743
|
resultToValue,
|
|
3739
3744
|
valueToData,
|
|
3740
3745
|
provisionalValue,
|
|
3746
|
+
inheritData = true,
|
|
3741
3747
|
dataDefault,
|
|
3742
3748
|
data = dataDefault,
|
|
3743
3749
|
|
|
@@ -4414,6 +4420,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4414
4420
|
const privateProperties = {
|
|
4415
4421
|
valueInitial,
|
|
4416
4422
|
provisionalValue,
|
|
4423
|
+
inheritData,
|
|
4417
4424
|
|
|
4418
4425
|
performRun,
|
|
4419
4426
|
performReset,
|
|
@@ -4446,7 +4453,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4446
4453
|
* a list of filters changes, providing real-time results without user interaction.
|
|
4447
4454
|
* @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
|
|
4448
4455
|
* By default (true), the proxy carries over the previous target's value and error into the new action —
|
|
4449
|
-
* unless the action declares
|
|
4456
|
+
* unless the action itself declares `inheritData: false` (createAction), which a resource `GET` does.
|
|
4450
4457
|
* This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
|
|
4451
4458
|
* new action completes, not when it starts loading. Code that needs to distinguish loading state can still
|
|
4452
4459
|
* check `action.runningState`, while code that just reads `action.data` always sees the most recent
|
|
@@ -4493,12 +4500,12 @@ const createActionProxyFromSignal = (
|
|
|
4493
4500
|
let currentActionPrivateProperties = getActionPrivateProperties(action);
|
|
4494
4501
|
let actionTargetPreviousWeakRef = null;
|
|
4495
4502
|
|
|
4496
|
-
// An action
|
|
4497
|
-
//
|
|
4498
|
-
//
|
|
4499
|
-
//
|
|
4503
|
+
// An action whose answer for other params would be false for these (a
|
|
4504
|
+
// resource GET: another row) says so itself, and is not handed it. What it
|
|
4505
|
+
// does not know stays unknown: a skeleton, or its provisionalValue, never a
|
|
4506
|
+
// different row.
|
|
4500
4507
|
const inheritsAnswer =
|
|
4501
|
-
inheritData &&
|
|
4508
|
+
inheritData && getActionPrivateProperties(action).inheritData;
|
|
4502
4509
|
const createTarget = (params) => {
|
|
4503
4510
|
if (inheritsAnswer) {
|
|
4504
4511
|
const previousActionTarget = actionTargetPreviousWeakRef?.deref();
|
|
@@ -10649,6 +10656,9 @@ const paramsTargetCanPlace = (redirectRoute, urlParams) => {
|
|
|
10649
10656
|
|
|
10650
10657
|
let setupRoutesCalled = false;
|
|
10651
10658
|
let activeRouteSet = null;
|
|
10659
|
+
// True once the routes have been matched against an address. Before that, no
|
|
10660
|
+
// route matching says nothing about the page: nothing has been asked yet.
|
|
10661
|
+
const routedSignal = signal(false);
|
|
10652
10662
|
|
|
10653
10663
|
const routePreloadMap = new WeakMap();
|
|
10654
10664
|
const registerRoutePreload = (route, preload) => {
|
|
@@ -11073,6 +11083,7 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
11073
11083
|
}
|
|
11074
11084
|
// Reset flag after URL -> Signal synchronization is complete
|
|
11075
11085
|
isUpdatingRoutesFromUrl = false;
|
|
11086
|
+
routedSignal.value = true;
|
|
11076
11087
|
Object.assign(returnValue, { matchingRouteSet });
|
|
11077
11088
|
for (const [route, repairedParams] of repairedParamsByRoute) {
|
|
11078
11089
|
route.replaceParams(repairedParams, {
|
|
@@ -11122,6 +11133,7 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
11122
11133
|
droppingRouteSet = null;
|
|
11123
11134
|
setupRoutesCalled = false;
|
|
11124
11135
|
activeRouteSet = null;
|
|
11136
|
+
routedSignal.value = false;
|
|
11125
11137
|
};
|
|
11126
11138
|
return {
|
|
11127
11139
|
updateRoutes,
|
|
@@ -11207,6 +11219,42 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
11207
11219
|
return `${urlObject.origin}${urlObject.pathname}`;
|
|
11208
11220
|
};
|
|
11209
11221
|
|
|
11222
|
+
/**
|
|
11223
|
+
* What a component can learn about the page it is rendered in, from the route
|
|
11224
|
+
* container rendering that page (see useContentKeepingLeavingPages in
|
|
11225
|
+
* route.jsx). `null` outside of any route container.
|
|
11226
|
+
*
|
|
11227
|
+
* - `isShown()` — whether the container would still show the page. False as
|
|
11228
|
+
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
11229
|
+
* hidden until the route transition leaving it is over, and nothing it reads
|
|
11230
|
+
* is worth a render any more. False for good once it is kept while leaving: a
|
|
11231
|
+
* return to its route mounts the page anew. Asked outside any render — by a
|
|
11232
|
+
* subscription deciding whether to re-render — and it reads the routes
|
|
11233
|
+
* untracked, so asking subscribes nobody to them.
|
|
11234
|
+
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
11235
|
+
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
11236
|
+
* bar) steps out rather than showing with the page arriving.
|
|
11237
|
+
*/
|
|
11238
|
+
const RoutePageContext = createContext(null);
|
|
11239
|
+
|
|
11240
|
+
/**
|
|
11241
|
+
* A container has put its page on screen — or as much of it as it can.
|
|
11242
|
+
*
|
|
11243
|
+
* A route matching is a signal changing, and the page it selects reaches the
|
|
11244
|
+
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
11245
|
+
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
11246
|
+
* as it has been decided (a travel about to have its picture taken by the
|
|
11247
|
+
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
11248
|
+
*
|
|
11249
|
+
* A page waiting on data is announced too, by the boundary showing its loading
|
|
11250
|
+
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
11251
|
+
* screen is what the browser is about to take a picture of, and a page that
|
|
11252
|
+
* cannot render yet would otherwise be waited on until the transition dies of
|
|
11253
|
+
* it. It lives in a module of its own for that: the async layer says it as much
|
|
11254
|
+
* as the router does, and neither can import the other.
|
|
11255
|
+
*/
|
|
11256
|
+
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
11257
|
+
|
|
11210
11258
|
const documentStateSignal = signal(null);
|
|
11211
11259
|
const useDocumentState = () => {
|
|
11212
11260
|
return documentStateSignal.value;
|
|
@@ -11466,24 +11514,6 @@ const readTopInWindow = element => {
|
|
|
11466
11514
|
return element.getBoundingClientRect().top;
|
|
11467
11515
|
};
|
|
11468
11516
|
|
|
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
11517
|
/**
|
|
11488
11518
|
* Where a page was left, given back when one comes back to it.
|
|
11489
11519
|
*
|
|
@@ -13011,6 +13041,7 @@ const warnAboutOrphanGeneratedKeys = () => {
|
|
|
13011
13041
|
|
|
13012
13042
|
const NO_OP = () => {};
|
|
13013
13043
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
13044
|
+
const countRenders$1 = (count) => count + 1;
|
|
13014
13045
|
// What the computed below answers for a key the document state does not hold:
|
|
13015
13046
|
// key presence is the flag, and the value under a present key may be anything,
|
|
13016
13047
|
// undefined included.
|
|
@@ -13039,7 +13070,36 @@ const useNavStateBasic = (
|
|
|
13039
13070
|
[id],
|
|
13040
13071
|
);
|
|
13041
13072
|
const keyValue = keyValueComputed.value;
|
|
13042
|
-
|
|
13073
|
+
// A page the address has left keeps reading the entry it was shown on until
|
|
13074
|
+
// it is taken down (kept hidden while a route transition plays): the new
|
|
13075
|
+
// entry would close what was open there — a Picker as a cancel — where the
|
|
13076
|
+
// same page taken down at once is only unmounted. Its render still happens:
|
|
13077
|
+
// the state is written before the routes say the page is left.
|
|
13078
|
+
const routePage = useContext(RoutePageContext);
|
|
13079
|
+
const keyValueReadRef = useRef(keyValue);
|
|
13080
|
+
const keyValueWithheld =
|
|
13081
|
+
keyValue !== keyValueReadRef.current &&
|
|
13082
|
+
routePage !== null &&
|
|
13083
|
+
!routePage.isShown();
|
|
13084
|
+
if (!keyValueWithheld) {
|
|
13085
|
+
keyValueReadRef.current = keyValue;
|
|
13086
|
+
}
|
|
13087
|
+
const keyValueRead = keyValueReadRef.current;
|
|
13088
|
+
// Owed rather than dropped, as in useAsyncData: the address can come back
|
|
13089
|
+
// before the container renders (a redirect straight back), and the page then
|
|
13090
|
+
// stays on screen.
|
|
13091
|
+
const [, rerender] = useReducer(countRenders$1, 0);
|
|
13092
|
+
useEffect(() => {
|
|
13093
|
+
if (!keyValueWithheld) {
|
|
13094
|
+
return undefined;
|
|
13095
|
+
}
|
|
13096
|
+
return observeRouteRender(() => {
|
|
13097
|
+
if (routePage.isShown()) {
|
|
13098
|
+
rerender();
|
|
13099
|
+
}
|
|
13100
|
+
});
|
|
13101
|
+
}, [keyValueWithheld]);
|
|
13102
|
+
const keyInState = keyValueRead !== KEY_NOT_IN_STATE;
|
|
13043
13103
|
const onLeaveRef = useRef(onLeave);
|
|
13044
13104
|
onLeaveRef.current = onLeave;
|
|
13045
13105
|
const prevKeyInStateRef = useRef(keyInState);
|
|
@@ -13074,7 +13134,7 @@ const useNavStateBasic = (
|
|
|
13074
13134
|
effectiveType = "replace";
|
|
13075
13135
|
}
|
|
13076
13136
|
|
|
13077
|
-
const currentValue = keyInState ?
|
|
13137
|
+
const currentValue = keyInState ? keyValueRead : defaultValue;
|
|
13078
13138
|
|
|
13079
13139
|
if (debug) {
|
|
13080
13140
|
console.debug(`useNavState(${id}) current value is ${currentValue}`);
|
|
@@ -13142,6 +13202,8 @@ const useNavStateBasic = (
|
|
|
13142
13202
|
/**
|
|
13143
13203
|
* Stores a named value in the browser's document state and returns it reactively.
|
|
13144
13204
|
* The component re-renders whenever the value changes (navigation, back/forward button).
|
|
13205
|
+
* In a page the address has left — kept hidden while a route transition plays —
|
|
13206
|
+
* the value stays the one of the entry that page was shown on.
|
|
13145
13207
|
*
|
|
13146
13208
|
* @param {string} id
|
|
13147
13209
|
* Unique key used to store the value in document state. Must be stable across renders.
|
|
@@ -15576,6 +15638,13 @@ const createOpenController = (
|
|
|
15576
15638
|
change();
|
|
15577
15639
|
};
|
|
15578
15640
|
const runChange = (change, { opened, event }) => {
|
|
15641
|
+
// What the controller answers about itself is the decision, written before
|
|
15642
|
+
// the change runs, whatever runs it. Whoever asks meanwhile reads it: the
|
|
15643
|
+
// render an opening forces (onOpen, mountContent) reads the open state
|
|
15644
|
+
// the caller holds, and an onOpen writing the popup's own `signal` has
|
|
15645
|
+
// useOpenPropsEffectOnOpenController asking for this very opening — a
|
|
15646
|
+
// second one, nested in the first, if `opened` still said closed.
|
|
15647
|
+
controller.opened = opened;
|
|
15579
15648
|
const applyChange = () => {
|
|
15580
15649
|
// Recorded with the change itself: what the DOM shows is what a render
|
|
15581
15650
|
// landing between the ask and the picture must draw (see `openedInDom`).
|
|
@@ -15591,12 +15660,8 @@ const createOpenController = (
|
|
|
15591
15660
|
applyChange();
|
|
15592
15661
|
return;
|
|
15593
15662
|
}
|
|
15594
|
-
//
|
|
15595
|
-
//
|
|
15596
|
-
// useOpenPropsEffectOnOpenController, which writes it back into the
|
|
15597
|
-
// caller's own signal), and a request arriving before the change lands
|
|
15598
|
-
// runs it first rather than reading a DOM that disagrees.
|
|
15599
|
-
controller.opened = opened;
|
|
15663
|
+
// A request arriving before the change lands runs it first rather than
|
|
15664
|
+
// reading a DOM that disagrees.
|
|
15600
15665
|
changeAwaitingTransition = applyChange;
|
|
15601
15666
|
transitionChange(
|
|
15602
15667
|
() => {
|
|
@@ -15609,7 +15674,6 @@ const createOpenController = (
|
|
|
15609
15674
|
};
|
|
15610
15675
|
|
|
15611
15676
|
const performClose = (closeEvent) => {
|
|
15612
|
-
controller.opened = false;
|
|
15613
15677
|
// Read before any close effect touches the DOM: closing a native <dialog>
|
|
15614
15678
|
// hands the focus back to whatever held it at showModal() time, so by the
|
|
15615
15679
|
// time the close cleanup runs, the popup's content has already lost the
|
|
@@ -15877,7 +15941,6 @@ const createOpenController = (
|
|
|
15877
15941
|
// told it opened right after (see popup_content_mount.js and
|
|
15878
15942
|
// use_displayed_layout_effect.js).
|
|
15879
15943
|
controller.openedInDom = true;
|
|
15880
|
-
controller.opened = true;
|
|
15881
15944
|
// Which press it opened during, so the release of that press is not
|
|
15882
15945
|
// read as somebody dismissing it (see openedDuringThisPress).
|
|
15883
15946
|
controller.pressCountAtOpen = pressCount;
|
|
@@ -16322,8 +16385,10 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
16322
16385
|
return undefined;
|
|
16323
16386
|
}
|
|
16324
16387
|
// Skip when the controller is already in the desired state.
|
|
16325
|
-
// openController.opened
|
|
16326
|
-
//
|
|
16388
|
+
// openController.opened is what the controller decided, written before an
|
|
16389
|
+
// opening renders anything (see runChange), so it is the authoritative
|
|
16390
|
+
// check against feedback loops — the popup's own onOpen writing its
|
|
16391
|
+
// `signal` included.
|
|
16327
16392
|
if (open === openController.opened) {
|
|
16328
16393
|
return undefined;
|
|
16329
16394
|
}
|
|
@@ -20293,18 +20358,17 @@ const MIN_CONSTRAINT = {
|
|
|
20293
20358
|
if (cmp === null || cmp >= 0) {
|
|
20294
20359
|
return null;
|
|
20295
20360
|
}
|
|
20296
|
-
|
|
20297
|
-
return naviI18n("constraint.min.duration.default", {
|
|
20298
|
-
min: formatDuration(min),
|
|
20299
|
-
});
|
|
20361
|
+
return boundMessage(field, "min");
|
|
20300
20362
|
}
|
|
20301
20363
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
20302
20364
|
return null;
|
|
20303
20365
|
}
|
|
20304
|
-
const
|
|
20305
|
-
|
|
20366
|
+
const min = field.controlHostProps.min;
|
|
20367
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
20368
|
+
if (min === undefined || min === null || min === "") {
|
|
20306
20369
|
return null;
|
|
20307
20370
|
}
|
|
20371
|
+
const minString = String(min);
|
|
20308
20372
|
const type = field.controlHostProps.type;
|
|
20309
20373
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20310
20374
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -20320,40 +20384,16 @@ const MIN_CONSTRAINT = {
|
|
|
20320
20384
|
if (isNaN(numericValue)) {
|
|
20321
20385
|
return null;
|
|
20322
20386
|
}
|
|
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
|
-
});
|
|
20387
|
+
if (numericValue >= minNumber) {
|
|
20388
|
+
return null;
|
|
20347
20389
|
}
|
|
20348
|
-
return
|
|
20390
|
+
return boundMessage(field, "min");
|
|
20349
20391
|
}
|
|
20350
20392
|
if (type === "time") {
|
|
20351
20393
|
const [minHours, minMinutes] = minString.split(":").map(Number);
|
|
20352
20394
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
20353
20395
|
if (hours < minHours || (hours === minHours && minutes < minMinutes)) {
|
|
20354
|
-
return
|
|
20355
|
-
min: minString,
|
|
20356
|
-
});
|
|
20396
|
+
return boundMessage(field, "min");
|
|
20357
20397
|
}
|
|
20358
20398
|
return null;
|
|
20359
20399
|
}
|
|
@@ -20361,18 +20401,13 @@ const MIN_CONSTRAINT = {
|
|
|
20361
20401
|
// so they never need a min/max validation message.
|
|
20362
20402
|
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
20363
20403
|
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
|
-
});
|
|
20404
|
+
return boundMessage(field, "min");
|
|
20371
20405
|
}
|
|
20372
20406
|
return null;
|
|
20373
20407
|
}
|
|
20374
20408
|
return null;
|
|
20375
20409
|
},
|
|
20410
|
+
messageAtBound: (field) => boundMessage(field, "min"),
|
|
20376
20411
|
};
|
|
20377
20412
|
CONSTRAINT_ATTRIBUTE_SET.add("min");
|
|
20378
20413
|
|
|
@@ -20392,18 +20427,17 @@ const MAX_CONSTRAINT = {
|
|
|
20392
20427
|
if (cmp === null || cmp <= 0) {
|
|
20393
20428
|
return null;
|
|
20394
20429
|
}
|
|
20395
|
-
|
|
20396
|
-
return naviI18n("constraint.max.duration.default", {
|
|
20397
|
-
max: formatDuration(max),
|
|
20398
|
-
});
|
|
20430
|
+
return boundMessage(field, "max");
|
|
20399
20431
|
}
|
|
20400
20432
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
20401
20433
|
return null;
|
|
20402
20434
|
}
|
|
20403
|
-
const
|
|
20404
|
-
|
|
20435
|
+
const max = field.controlHostProps.max;
|
|
20436
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
20437
|
+
if (max === undefined || max === null || max === "") {
|
|
20405
20438
|
return null;
|
|
20406
20439
|
}
|
|
20440
|
+
const maxString = String(max);
|
|
20407
20441
|
const type = field.controlHostProps.type;
|
|
20408
20442
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20409
20443
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -20422,38 +20456,13 @@ const MAX_CONSTRAINT = {
|
|
|
20422
20456
|
if (numericValue <= maxNumber) {
|
|
20423
20457
|
return null;
|
|
20424
20458
|
}
|
|
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
|
-
});
|
|
20459
|
+
return boundMessage(field, "max");
|
|
20449
20460
|
}
|
|
20450
20461
|
if (type === "time") {
|
|
20451
20462
|
const [maxHours, maxMinutes] = maxString.split(":").map(Number);
|
|
20452
20463
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
20453
20464
|
if (hours > maxHours || (hours === maxHours && minutes > maxMinutes)) {
|
|
20454
|
-
return
|
|
20455
|
-
max: maxString,
|
|
20456
|
-
});
|
|
20465
|
+
return boundMessage(field, "max");
|
|
20457
20466
|
}
|
|
20458
20467
|
return null;
|
|
20459
20468
|
}
|
|
@@ -20461,17 +20470,11 @@ const MAX_CONSTRAINT = {
|
|
|
20461
20470
|
if (valueAsString <= maxString) {
|
|
20462
20471
|
return null;
|
|
20463
20472
|
}
|
|
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
|
-
});
|
|
20473
|
+
return boundMessage(field, "max");
|
|
20472
20474
|
}
|
|
20473
20475
|
return null;
|
|
20474
20476
|
},
|
|
20477
|
+
messageAtBound: (field) => boundMessage(field, "max"),
|
|
20475
20478
|
};
|
|
20476
20479
|
CONSTRAINT_ATTRIBUTE_SET.add("max");
|
|
20477
20480
|
|
|
@@ -20666,6 +20669,51 @@ const formatMsToTime = (ms, showSeconds) => {
|
|
|
20666
20669
|
return `${hh}:${mm}:${ss}`;
|
|
20667
20670
|
};
|
|
20668
20671
|
|
|
20672
|
+
// What a bound says, whatever the value: the sentence `check` gives a value
|
|
20673
|
+
// past it, and the one a spin gives when its chevron is refused there
|
|
20674
|
+
// (picker_spin.jsx) — one sentence, so the two refuse in the same words. null
|
|
20675
|
+
// for a type that has nothing to say about a bound.
|
|
20676
|
+
const boundMessage = (field, bound) => {
|
|
20677
|
+
const boundValue = field.controlHostProps[bound];
|
|
20678
|
+
if (field.controlType === "duration_group") {
|
|
20679
|
+
return naviI18n(`constraint.${bound}.duration.default`, {
|
|
20680
|
+
[bound]: formatDuration(boundValue),
|
|
20681
|
+
});
|
|
20682
|
+
}
|
|
20683
|
+
const type = field.controlHostProps.type;
|
|
20684
|
+
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
20685
|
+
if (isNumberInput(type, naviInputType)) {
|
|
20686
|
+
const numberKind = NUMBER_BOUND_KIND_SET.has(naviInputType)
|
|
20687
|
+
? naviInputType
|
|
20688
|
+
: "number";
|
|
20689
|
+
return naviI18n(`constraint.${bound}.${numberKind}.default`, {
|
|
20690
|
+
[bound]: String(boundValue),
|
|
20691
|
+
});
|
|
20692
|
+
}
|
|
20693
|
+
if (type === "time") {
|
|
20694
|
+
return naviI18n(`constraint.${bound}.time.default`, {
|
|
20695
|
+
[bound]: boundValue,
|
|
20696
|
+
});
|
|
20697
|
+
}
|
|
20698
|
+
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
20699
|
+
if (boundValue === getTodayIso(type)) {
|
|
20700
|
+
return naviI18n(`constraint.${bound}.date.today.default`);
|
|
20701
|
+
}
|
|
20702
|
+
return naviI18n(`constraint.${bound}.date.default`, {
|
|
20703
|
+
[bound]: formatDateIso(boundValue, type),
|
|
20704
|
+
});
|
|
20705
|
+
}
|
|
20706
|
+
return null;
|
|
20707
|
+
};
|
|
20708
|
+
// The numbers whose bound is said with what they count ("Max 23 heures");
|
|
20709
|
+
// any other is "Ce nombre".
|
|
20710
|
+
const NUMBER_BOUND_KIND_SET = new Set([
|
|
20711
|
+
"hour",
|
|
20712
|
+
"minute",
|
|
20713
|
+
"second",
|
|
20714
|
+
"percentage",
|
|
20715
|
+
]);
|
|
20716
|
+
|
|
20669
20717
|
const getTodayIso = (inputType) => {
|
|
20670
20718
|
const now = new Date();
|
|
20671
20719
|
const yyyy = now.getFullYear();
|
|
@@ -26690,6 +26738,171 @@ const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
|
|
|
26690
26738
|
return childStore !== undefined && childStore === getChildStore(ownerId);
|
|
26691
26739
|
};
|
|
26692
26740
|
|
|
26741
|
+
/*
|
|
26742
|
+
* What a previous document kept of the page it was on (see keepPageOnScreen in
|
|
26743
|
+
* nav/page_kept.js), waiting for the reads that draw it again, and what those
|
|
26744
|
+
* reads are made of on the way out: rows with their relations inline.
|
|
26745
|
+
*
|
|
26746
|
+
* A kept read is found by the read's name and the params it was asked with
|
|
26747
|
+
* (getKeptReadKey). It is handed out once: the first run of the read draws it
|
|
26748
|
+
* while its request is out, and from then on the store holds the rows. Handed
|
|
26749
|
+
* out again, after a reset say, it would write the previous document's rows
|
|
26750
|
+
* over a fresher answer.
|
|
26751
|
+
*
|
|
26752
|
+
* This module is the half the resources see: `resource()` and the range reader
|
|
26753
|
+
* take from it, and say which of their reads can be kept at all. Which reads
|
|
26754
|
+
* are on the page on screen, and what the slot is written into, is
|
|
26755
|
+
* page_kept.js.
|
|
26756
|
+
*/
|
|
26757
|
+
|
|
26758
|
+
// key → what the read answered in the previous document, not handed out yet
|
|
26759
|
+
const offeredReadMap = new Map();
|
|
26760
|
+
const offerKeptReads = (entryMap) => {
|
|
26761
|
+
offeredReadMap.clear();
|
|
26762
|
+
for (const [key, entry] of entryMap) {
|
|
26763
|
+
offeredReadMap.set(key, entry);
|
|
26764
|
+
}
|
|
26765
|
+
};
|
|
26766
|
+
const withdrawKeptReads = () => {
|
|
26767
|
+
offeredReadMap.clear();
|
|
26768
|
+
};
|
|
26769
|
+
const takeKeptRead = (key) => {
|
|
26770
|
+
const entry = offeredReadMap.get(key);
|
|
26771
|
+
if (entry === undefined) {
|
|
26772
|
+
return undefined;
|
|
26773
|
+
}
|
|
26774
|
+
offeredReadMap.delete(key);
|
|
26775
|
+
return entry;
|
|
26776
|
+
};
|
|
26777
|
+
|
|
26778
|
+
// The root actions whose answer is rows of a store, written and read back the
|
|
26779
|
+
// same way: a resource's GET and GET_MANY. Anything else a route runs (a page's
|
|
26780
|
+
// code, a computation) has nothing a later document could draw.
|
|
26781
|
+
const keepableReadWeakSet = new WeakSet();
|
|
26782
|
+
const markKeepableRead = (rootAction) => {
|
|
26783
|
+
keepableReadWeakSet.add(rootAction);
|
|
26784
|
+
};
|
|
26785
|
+
const isKeepableRead = (action) => {
|
|
26786
|
+
const rootAction = action.rootAction || action;
|
|
26787
|
+
return keepableReadWeakSet.has(rootAction);
|
|
26788
|
+
};
|
|
26789
|
+
|
|
26790
|
+
// The range readers a <List.Items> on screen reads through, each with the number
|
|
26791
|
+
// of lists reading it: the reads of the page a route action does not cover.
|
|
26792
|
+
const shownRangeReadersSignal = signal(new Map());
|
|
26793
|
+
const showRangeReader = (reader) => {
|
|
26794
|
+
const shownMap = new Map(shownRangeReadersSignal.peek());
|
|
26795
|
+
shownMap.set(reader, (shownMap.get(reader) || 0) + 1);
|
|
26796
|
+
shownRangeReadersSignal.value = shownMap;
|
|
26797
|
+
return () => {
|
|
26798
|
+
const shownMapNow = new Map(shownRangeReadersSignal.peek());
|
|
26799
|
+
const count = shownMapNow.get(reader);
|
|
26800
|
+
if (count === 1) {
|
|
26801
|
+
shownMapNow.delete(reader);
|
|
26802
|
+
} else {
|
|
26803
|
+
shownMapNow.set(reader, count - 1);
|
|
26804
|
+
}
|
|
26805
|
+
shownRangeReadersSignal.value = shownMapNow;
|
|
26806
|
+
};
|
|
26807
|
+
};
|
|
26808
|
+
|
|
26809
|
+
// A row already in the store is the fresher one: the kept copy only stands in
|
|
26810
|
+
// for rows nothing has answered yet. Its relations are written as they were
|
|
26811
|
+
// kept, which a real answer corrects like any other.
|
|
26812
|
+
const upsertKeptItem = (store, keptItem) => {
|
|
26813
|
+
const id = keptItem[store.idKey];
|
|
26814
|
+
if (id !== undefined) {
|
|
26815
|
+
const existing = store.select(id);
|
|
26816
|
+
if (existing) {
|
|
26817
|
+
return existing;
|
|
26818
|
+
}
|
|
26819
|
+
}
|
|
26820
|
+
return store.upsert(keptItem);
|
|
26821
|
+
};
|
|
26822
|
+
|
|
26823
|
+
const getKeptReadKey = (name, params) => {
|
|
26824
|
+
return `${name}|${getParamsKey(params)}`;
|
|
26825
|
+
};
|
|
26826
|
+
// `true` is what a route action without params is bound to (see routeAction),
|
|
26827
|
+
// and a key holding undefined says nothing a missing one does not.
|
|
26828
|
+
const getParamsKey = (params) => {
|
|
26829
|
+
if (params === undefined || params === NO_PARAMS || params === true) {
|
|
26830
|
+
return "";
|
|
26831
|
+
}
|
|
26832
|
+
if (isPlainParams(params)) {
|
|
26833
|
+
const keys = Object.keys(params).filter((key) => params[key] !== undefined);
|
|
26834
|
+
if (keys.length === 0) {
|
|
26835
|
+
return "";
|
|
26836
|
+
}
|
|
26837
|
+
}
|
|
26838
|
+
return stableStringify(params);
|
|
26839
|
+
};
|
|
26840
|
+
const isPlainParams = (value) => {
|
|
26841
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26842
|
+
};
|
|
26843
|
+
const stableStringify = (value) => {
|
|
26844
|
+
if (value === null || typeof value !== "object") {
|
|
26845
|
+
return JSON.stringify(value);
|
|
26846
|
+
}
|
|
26847
|
+
if (Array.isArray(value)) {
|
|
26848
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
26849
|
+
}
|
|
26850
|
+
const keys = Object.keys(value)
|
|
26851
|
+
.filter((key) => value[key] !== undefined)
|
|
26852
|
+
.sort();
|
|
26853
|
+
const parts = [];
|
|
26854
|
+
for (const key of keys) {
|
|
26855
|
+
parts.push(`${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
26856
|
+
}
|
|
26857
|
+
return `{${parts.join(",")}}`;
|
|
26858
|
+
};
|
|
26859
|
+
|
|
26860
|
+
// What a read's data is made of, ready to be written: a row, or the rows of a
|
|
26861
|
+
// list, each with its relations inline.
|
|
26862
|
+
const serializeData = (data) => {
|
|
26863
|
+
if (Array.isArray(data)) {
|
|
26864
|
+
return data.map((item) => serializeItem(item, new Set()));
|
|
26865
|
+
}
|
|
26866
|
+
return serializeItem(data, new Set());
|
|
26867
|
+
};
|
|
26868
|
+
// The item with its relations inline: a relation value stands for what its
|
|
26869
|
+
// signal holds (the child row, the child rows, or nothing), a row reached
|
|
26870
|
+
// twice on the same path is written as its id — the setters accept both.
|
|
26871
|
+
const serializeItem = (item, ancestorSet) => {
|
|
26872
|
+
ancestorSet.add(item);
|
|
26873
|
+
const serialized = {};
|
|
26874
|
+
for (const key of Object.keys(item)) {
|
|
26875
|
+
serialized[key] = serializeValue(item[key], ancestorSet);
|
|
26876
|
+
}
|
|
26877
|
+
ancestorSet.delete(item);
|
|
26878
|
+
return serialized;
|
|
26879
|
+
};
|
|
26880
|
+
const serializeValue = (value, ancestorSet) => {
|
|
26881
|
+
if (value === null || typeof value !== "object") {
|
|
26882
|
+
return value;
|
|
26883
|
+
}
|
|
26884
|
+
if (Array.isArray(value)) {
|
|
26885
|
+
return value.map((entry) => serializeValue(entry, ancestorSet));
|
|
26886
|
+
}
|
|
26887
|
+
const objectSignal = value[SYMBOL_OBJECT_SIGNAL];
|
|
26888
|
+
if (objectSignal) {
|
|
26889
|
+
const held = objectSignal.value;
|
|
26890
|
+
if (held === undefined || held === null) {
|
|
26891
|
+
return null;
|
|
26892
|
+
}
|
|
26893
|
+
return serializeValue(held, ancestorSet);
|
|
26894
|
+
}
|
|
26895
|
+
if (typeof value.toJSON === "function") {
|
|
26896
|
+
return value;
|
|
26897
|
+
}
|
|
26898
|
+
if (ancestorSet.has(value)) {
|
|
26899
|
+
return Object.hasOwn(value, SYMBOL_IDENTITY)
|
|
26900
|
+
? value[SYMBOL_IDENTITY]
|
|
26901
|
+
: null;
|
|
26902
|
+
}
|
|
26903
|
+
return serializeItem(value, ancestorSet);
|
|
26904
|
+
};
|
|
26905
|
+
|
|
26693
26906
|
const paramScopeWeakSet = createIterableWeakSet();
|
|
26694
26907
|
let paramScopeIdCounter = 0;
|
|
26695
26908
|
const getParamScope = (params) => {
|
|
@@ -26860,66 +27073,6 @@ const createResourcePersistence = (
|
|
|
26860
27073
|
};
|
|
26861
27074
|
};
|
|
26862
27075
|
|
|
26863
|
-
const getParamsKey = (params) => {
|
|
26864
|
-
if (params === undefined || params === NO_PARAMS) {
|
|
26865
|
-
return "";
|
|
26866
|
-
}
|
|
26867
|
-
return stableStringify(params);
|
|
26868
|
-
};
|
|
26869
|
-
|
|
26870
|
-
const stableStringify = (value) => {
|
|
26871
|
-
if (value === null || typeof value !== "object") {
|
|
26872
|
-
return JSON.stringify(value);
|
|
26873
|
-
}
|
|
26874
|
-
if (Array.isArray(value)) {
|
|
26875
|
-
return `[${value.map(stableStringify).join(",")}]`;
|
|
26876
|
-
}
|
|
26877
|
-
const keys = Object.keys(value).sort();
|
|
26878
|
-
const parts = [];
|
|
26879
|
-
for (const key of keys) {
|
|
26880
|
-
parts.push(`${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
26881
|
-
}
|
|
26882
|
-
return `{${parts.join(",")}}`;
|
|
26883
|
-
};
|
|
26884
|
-
|
|
26885
|
-
// The item with its relations inline: a relation value stands for what its
|
|
26886
|
-
// signal holds (the child row, the child rows, or nothing), a row reached
|
|
26887
|
-
// twice on the same path is written as its id — the setters accept both.
|
|
26888
|
-
const serializeItem = (item, ancestorSet) => {
|
|
26889
|
-
ancestorSet.add(item);
|
|
26890
|
-
const serialized = {};
|
|
26891
|
-
for (const key of Object.keys(item)) {
|
|
26892
|
-
serialized[key] = serializeValue(item[key], ancestorSet);
|
|
26893
|
-
}
|
|
26894
|
-
ancestorSet.delete(item);
|
|
26895
|
-
return serialized;
|
|
26896
|
-
};
|
|
26897
|
-
const serializeValue = (value, ancestorSet) => {
|
|
26898
|
-
if (value === null || typeof value !== "object") {
|
|
26899
|
-
return value;
|
|
26900
|
-
}
|
|
26901
|
-
if (Array.isArray(value)) {
|
|
26902
|
-
return value.map((entry) => serializeValue(entry, ancestorSet));
|
|
26903
|
-
}
|
|
26904
|
-
const objectSignal = value[SYMBOL_OBJECT_SIGNAL];
|
|
26905
|
-
if (objectSignal) {
|
|
26906
|
-
const held = objectSignal.value;
|
|
26907
|
-
if (held === undefined || held === null) {
|
|
26908
|
-
return null;
|
|
26909
|
-
}
|
|
26910
|
-
return serializeValue(held, ancestorSet);
|
|
26911
|
-
}
|
|
26912
|
-
if (typeof value.toJSON === "function") {
|
|
26913
|
-
return value;
|
|
26914
|
-
}
|
|
26915
|
-
if (ancestorSet.has(value)) {
|
|
26916
|
-
return Object.hasOwn(value, SYMBOL_IDENTITY)
|
|
26917
|
-
? value[SYMBOL_IDENTITY]
|
|
26918
|
-
: null;
|
|
26919
|
-
}
|
|
26920
|
-
return serializeItem(value, ancestorSet);
|
|
26921
|
-
};
|
|
26922
|
-
|
|
26923
27076
|
/*
|
|
26924
27077
|
* GET_RANGE: reading a resource one slice at a time, for a list that draws its
|
|
26925
27078
|
* rows as it goes (`<List.Items itemsAction>`).
|
|
@@ -26956,9 +27109,23 @@ const serializeValue = (value, ancestorSet) => {
|
|
|
26956
27109
|
* of what a rerun is for an action. Every reader made by `bindParams` shares
|
|
26957
27110
|
* the signal and the compositions of the one it comes from: the params say
|
|
26958
27111
|
* which slices are read, not which collection.
|
|
27112
|
+
*
|
|
27113
|
+
* A composition also outlives the document when a list on the page on screen
|
|
27114
|
+
* reads it (see keepPageOnScreen): the ranks around the window it draws are
|
|
27115
|
+
* written with their rows, and the first read of the next document finds them
|
|
27116
|
+
* as a composition left by a list that went away — the same return, after a
|
|
27117
|
+
* longer absence.
|
|
26959
27118
|
*/
|
|
26960
27119
|
|
|
26961
27120
|
|
|
27121
|
+
// The ranks kept of a composition for the next document: the window a list
|
|
27122
|
+
// draws, widened by at least one step on each side, and snapped to steps so a
|
|
27123
|
+
// scroll within one draws the same ranks and writes nothing.
|
|
27124
|
+
const KEPT_RANK_STEP = 20;
|
|
27125
|
+
// Counts the answers every composition took, so that one landing can be told
|
|
27126
|
+
// from one seen before (see readKept).
|
|
27127
|
+
let answerSequence = 0;
|
|
27128
|
+
|
|
26962
27129
|
const createRangeReader = (
|
|
26963
27130
|
actionName,
|
|
26964
27131
|
callback,
|
|
@@ -26967,6 +27134,9 @@ const createRangeReader = (
|
|
|
26967
27134
|
params: boundParams,
|
|
26968
27135
|
invalidationSignal = signal(0),
|
|
26969
27136
|
compositionSet = new Set(),
|
|
27137
|
+
// Bumped whenever what is kept of a composition for the next document
|
|
27138
|
+
// changes (see readKept); shared like the compositions themselves.
|
|
27139
|
+
keptVersionSignal = signal(0),
|
|
26970
27140
|
},
|
|
26971
27141
|
) => {
|
|
26972
27142
|
// Which composition this reader is about: the values its params hold, not its
|
|
@@ -26983,6 +27153,40 @@ const createRangeReader = (
|
|
|
26983
27153
|
}
|
|
26984
27154
|
return null;
|
|
26985
27155
|
};
|
|
27156
|
+
const keptKeyOf = (params) => getKeptReadKey(actionName, params);
|
|
27157
|
+
const bumpKeptVersion = () => {
|
|
27158
|
+
keptVersionSignal.value = keptVersionSignal.peek() + 1;
|
|
27159
|
+
};
|
|
27160
|
+
// The composition the previous document kept for these params, entering the
|
|
27161
|
+
// store and the compositions as if a list had just left it.
|
|
27162
|
+
const restoreKeptComposition = (params) => {
|
|
27163
|
+
const kept = takeKeptRead(keptKeyOf(params));
|
|
27164
|
+
if (!kept || typeof kept.count !== "number" || !kept.items) {
|
|
27165
|
+
return null;
|
|
27166
|
+
}
|
|
27167
|
+
const composition = {
|
|
27168
|
+
params,
|
|
27169
|
+
idByIndex: new Map(),
|
|
27170
|
+
count: kept.count,
|
|
27171
|
+
keptFrom: undefined,
|
|
27172
|
+
keptTo: undefined,
|
|
27173
|
+
answeredAt: 0,
|
|
27174
|
+
};
|
|
27175
|
+
for (const rank of Object.keys(kept.items)) {
|
|
27176
|
+
const index = Number(rank);
|
|
27177
|
+
const item = upsertKeptItem(store, kept.items[rank]);
|
|
27178
|
+
composition.idByIndex.set(index, item[store.idKey]);
|
|
27179
|
+
if (composition.keptFrom === undefined || index < composition.keptFrom) {
|
|
27180
|
+
composition.keptFrom = index;
|
|
27181
|
+
}
|
|
27182
|
+
if (composition.keptTo === undefined || index + 1 > composition.keptTo) {
|
|
27183
|
+
composition.keptTo = index + 1;
|
|
27184
|
+
}
|
|
27185
|
+
}
|
|
27186
|
+
compositionSet.add(composition);
|
|
27187
|
+
bumpKeptVersion();
|
|
27188
|
+
return composition;
|
|
27189
|
+
};
|
|
26986
27190
|
const readRange = async (range = {}) => {
|
|
26987
27191
|
const { signal, ...rangeParams } = range;
|
|
26988
27192
|
const paramsResolved = { ...resolveParams(boundParams), ...rangeParams };
|
|
@@ -27016,11 +27220,15 @@ const createRangeReader = (
|
|
|
27016
27220
|
readRange.invalidate = () => {
|
|
27017
27221
|
compositionSet.clear();
|
|
27018
27222
|
invalidationSignal.value = invalidationSignal.peek() + 1;
|
|
27223
|
+
bumpKeptVersion();
|
|
27019
27224
|
};
|
|
27020
27225
|
// The rows of a composition, drawn from the store: a rank whose row is gone
|
|
27021
27226
|
// from the store resolves to nothing and is asked for again.
|
|
27022
27227
|
readRange.readComposition = () => {
|
|
27023
|
-
const
|
|
27228
|
+
const params = currentParams();
|
|
27229
|
+
const composition = untracked(
|
|
27230
|
+
() => findComposition(params) || restoreKeptComposition(params),
|
|
27231
|
+
);
|
|
27024
27232
|
if (!composition) {
|
|
27025
27233
|
return null;
|
|
27026
27234
|
}
|
|
@@ -27044,18 +27252,85 @@ const createRangeReader = (
|
|
|
27044
27252
|
const params = currentParams();
|
|
27045
27253
|
let composition = findComposition(params);
|
|
27046
27254
|
if (!composition) {
|
|
27047
|
-
composition = {
|
|
27255
|
+
composition = {
|
|
27256
|
+
params,
|
|
27257
|
+
idByIndex: new Map(),
|
|
27258
|
+
count,
|
|
27259
|
+
keptFrom: undefined,
|
|
27260
|
+
keptTo: undefined,
|
|
27261
|
+
answeredAt: 0,
|
|
27262
|
+
};
|
|
27048
27263
|
compositionSet.add(composition);
|
|
27049
27264
|
} else if (replace) {
|
|
27050
27265
|
composition.idByIndex = new Map();
|
|
27051
27266
|
}
|
|
27052
27267
|
composition.count = count;
|
|
27268
|
+
answerSequence++;
|
|
27269
|
+
composition.answeredAt = answerSequence;
|
|
27053
27270
|
for (const [index, item] of byIndex) {
|
|
27054
27271
|
const id = item ? item[store.idKey] : undefined;
|
|
27055
27272
|
if (id !== undefined) {
|
|
27056
27273
|
composition.idByIndex.set(index, id);
|
|
27057
27274
|
}
|
|
27058
27275
|
}
|
|
27276
|
+
bumpKeptVersion();
|
|
27277
|
+
};
|
|
27278
|
+
// The ranks [windowFrom, windowTo) a list draws of this composition: what is
|
|
27279
|
+
// kept of it for the next document is around them (see readKept).
|
|
27280
|
+
readRange.frameComposition = (windowFrom, windowTo) => {
|
|
27281
|
+
const composition = findComposition(currentParams());
|
|
27282
|
+
if (!composition) {
|
|
27283
|
+
return;
|
|
27284
|
+
}
|
|
27285
|
+
let keptFrom =
|
|
27286
|
+
Math.floor((windowFrom - KEPT_RANK_STEP) / KEPT_RANK_STEP) *
|
|
27287
|
+
KEPT_RANK_STEP;
|
|
27288
|
+
if (keptFrom < 0) {
|
|
27289
|
+
keptFrom = 0;
|
|
27290
|
+
}
|
|
27291
|
+
const keptTo =
|
|
27292
|
+
Math.ceil((windowTo + KEPT_RANK_STEP) / KEPT_RANK_STEP) * KEPT_RANK_STEP;
|
|
27293
|
+
if (composition.keptFrom === keptFrom && composition.keptTo === keptTo) {
|
|
27294
|
+
return;
|
|
27295
|
+
}
|
|
27296
|
+
composition.keptFrom = keptFrom;
|
|
27297
|
+
composition.keptTo = keptTo;
|
|
27298
|
+
bumpKeptVersion();
|
|
27299
|
+
};
|
|
27300
|
+
// What a document keeps of the composition these params read, for the
|
|
27301
|
+
// next one (see keepPageOnScreen): the count, and the ranks around the
|
|
27302
|
+
// window last drawn with their rows inline. Read inside an effect, it
|
|
27303
|
+
// follows the composition and the rows. `answeredAt` grows with every answer
|
|
27304
|
+
// the composition takes (0: none in this document, restored from a kept one).
|
|
27305
|
+
readRange.readKept = () => {
|
|
27306
|
+
// eslint-disable-next-line no-unused-expressions
|
|
27307
|
+
keptVersionSignal.value;
|
|
27308
|
+
const params = resolveParams(boundParams);
|
|
27309
|
+
const key = keptKeyOf(params);
|
|
27310
|
+
const composition = findComposition(params);
|
|
27311
|
+
if (!composition || composition.count === undefined) {
|
|
27312
|
+
return { key, answeredAt: 0, readEntry: () => undefined };
|
|
27313
|
+
}
|
|
27314
|
+
const readEntry = () => {
|
|
27315
|
+
const items = {};
|
|
27316
|
+
for (const [index, id] of composition.idByIndex) {
|
|
27317
|
+
if (
|
|
27318
|
+
composition.keptFrom !== undefined &&
|
|
27319
|
+
index < composition.keptFrom
|
|
27320
|
+
) {
|
|
27321
|
+
continue;
|
|
27322
|
+
}
|
|
27323
|
+
if (composition.keptTo !== undefined && index >= composition.keptTo) {
|
|
27324
|
+
continue;
|
|
27325
|
+
}
|
|
27326
|
+
const item = store.select(id);
|
|
27327
|
+
if (item) {
|
|
27328
|
+
items[index] = serializeItem(item, new Set());
|
|
27329
|
+
}
|
|
27330
|
+
}
|
|
27331
|
+
return { count: composition.count, items };
|
|
27332
|
+
};
|
|
27333
|
+
return { key, answeredAt: composition.answeredAt, readEntry };
|
|
27059
27334
|
};
|
|
27060
27335
|
// The same trade a list makes with the rows it holds, applied to what is kept
|
|
27061
27336
|
// for the next mount. Two lists on one collection each trim by their own
|
|
@@ -27066,11 +27341,16 @@ const createRangeReader = (
|
|
|
27066
27341
|
if (!composition || composition.idByIndex.size <= budget) {
|
|
27067
27342
|
return;
|
|
27068
27343
|
}
|
|
27344
|
+
let trimmed = false;
|
|
27069
27345
|
for (const index of composition.idByIndex.keys()) {
|
|
27070
27346
|
if (index < keepFrom || index > keepTo) {
|
|
27071
27347
|
composition.idByIndex.delete(index);
|
|
27348
|
+
trimmed = true;
|
|
27072
27349
|
}
|
|
27073
27350
|
}
|
|
27351
|
+
if (trimmed) {
|
|
27352
|
+
bumpKeptVersion();
|
|
27353
|
+
}
|
|
27074
27354
|
};
|
|
27075
27355
|
// Memoized for the reasons an action's bindParams is (see actions.js): params
|
|
27076
27356
|
// and the reader they make have synchronized lifetimes, and params equal in
|
|
@@ -27086,6 +27366,7 @@ const createRangeReader = (
|
|
|
27086
27366
|
params: boundParams ? { ...boundParams, ...paramsToBind } : paramsToBind,
|
|
27087
27367
|
invalidationSignal,
|
|
27088
27368
|
compositionSet,
|
|
27369
|
+
keptVersionSignal,
|
|
27089
27370
|
});
|
|
27090
27371
|
readerByParams.set(paramsToBind, reader);
|
|
27091
27372
|
return reader;
|
|
@@ -28520,7 +28801,7 @@ const createRestActionFactoryForRoot = (
|
|
|
28520
28801
|
`${name}.${verb}`,
|
|
28521
28802
|
declarationSite,
|
|
28522
28803
|
);
|
|
28523
|
-
|
|
28804
|
+
const action = createAction(callback, {
|
|
28524
28805
|
name: `${name}.${verb}`,
|
|
28525
28806
|
meta: { verb, isMany: false, paramScope },
|
|
28526
28807
|
resultToValue: (result, action) => {
|
|
@@ -28556,17 +28837,33 @@ const createRestActionFactoryForRoot = (
|
|
|
28556
28837
|
},
|
|
28557
28838
|
valueToData: (itemId) => store.select(itemId),
|
|
28558
28839
|
// While the request is out, the row the store already holds for these
|
|
28559
|
-
// params is drawn
|
|
28840
|
+
// params is drawn, or the row the page kept from the previous document
|
|
28841
|
+
// (kept_reads.js): the action starts on the "refresh over a known
|
|
28560
28842
|
// answer" line of data_states.md rather than the first-load one.
|
|
28561
28843
|
provisionalValue:
|
|
28562
28844
|
verb === "GET"
|
|
28563
|
-
? (params) => {
|
|
28564
|
-
const item = untracked(() =>
|
|
28845
|
+
? (params, action) => {
|
|
28846
|
+
const item = untracked(() => {
|
|
28847
|
+
const itemForGet = findItemForGet(params);
|
|
28848
|
+
if (itemForGet) {
|
|
28849
|
+
return itemForGet;
|
|
28850
|
+
}
|
|
28851
|
+
const keptItem = takeKeptRead(
|
|
28852
|
+
getKeptReadKey(action.name, params),
|
|
28853
|
+
);
|
|
28854
|
+
return keptItem ? upsertKeptItem(store, keptItem) : null;
|
|
28855
|
+
});
|
|
28565
28856
|
return item ? item[idKey] : undefined;
|
|
28566
28857
|
}
|
|
28567
28858
|
: undefined,
|
|
28859
|
+
// Another params' answer is another row.
|
|
28860
|
+
inheritData: verb !== "GET",
|
|
28568
28861
|
completeSideEffect: onActionComplete,
|
|
28569
28862
|
});
|
|
28863
|
+
if (verb === "GET") {
|
|
28864
|
+
markKeepableRead(action);
|
|
28865
|
+
}
|
|
28866
|
+
return action;
|
|
28570
28867
|
};
|
|
28571
28868
|
const createActionAffectingManyItems = (
|
|
28572
28869
|
verb,
|
|
@@ -28581,7 +28878,7 @@ const createRestActionFactoryForRoot = (
|
|
|
28581
28878
|
return itemArray.map((item) => item[idKey]);
|
|
28582
28879
|
};
|
|
28583
28880
|
|
|
28584
|
-
|
|
28881
|
+
const action = createAction(callback, {
|
|
28585
28882
|
meta: { verb, isMany: true, paramScope },
|
|
28586
28883
|
name: `${name}.${verb}_MANY`,
|
|
28587
28884
|
resultToValue: applyResultToValue,
|
|
@@ -28589,6 +28886,25 @@ const createRestActionFactoryForRoot = (
|
|
|
28589
28886
|
// undefined so a screen can tell "not asked yet" from "answered: none".
|
|
28590
28887
|
valueToData: (idArray) =>
|
|
28591
28888
|
idArray === undefined ? undefined : store.selectAll(idArray),
|
|
28889
|
+
// The rows the page kept from the previous document (kept_reads.js),
|
|
28890
|
+
// drawn while the request is out. A binding moving to new params still
|
|
28891
|
+
// hands over the previous answer when there are none: a list standing
|
|
28892
|
+
// in for the next one beats a skeleton.
|
|
28893
|
+
provisionalValue:
|
|
28894
|
+
verb === "GET"
|
|
28895
|
+
? (params, action) =>
|
|
28896
|
+
untracked(() => {
|
|
28897
|
+
const keptItems = takeKeptRead(
|
|
28898
|
+
getKeptReadKey(action.name, params),
|
|
28899
|
+
);
|
|
28900
|
+
if (!Array.isArray(keptItems)) {
|
|
28901
|
+
return undefined;
|
|
28902
|
+
}
|
|
28903
|
+
return keptItems.map(
|
|
28904
|
+
(keptItem) => upsertKeptItem(store, keptItem)[idKey],
|
|
28905
|
+
);
|
|
28906
|
+
})
|
|
28907
|
+
: undefined,
|
|
28592
28908
|
completeSideEffect: (actionCompleted) => {
|
|
28593
28909
|
onActionComplete(actionCompleted);
|
|
28594
28910
|
if (
|
|
@@ -28604,6 +28920,10 @@ const createRestActionFactoryForRoot = (
|
|
|
28604
28920
|
return syncIdArrayOnRename(store, idKey, actionCompleted.valueSignal);
|
|
28605
28921
|
},
|
|
28606
28922
|
});
|
|
28923
|
+
if (verb === "GET") {
|
|
28924
|
+
markKeepableRead(action);
|
|
28925
|
+
}
|
|
28926
|
+
return action;
|
|
28607
28927
|
};
|
|
28608
28928
|
|
|
28609
28929
|
return createActionForRoot;
|
|
@@ -28614,10 +28934,12 @@ const createRestActionFactoryForRoot = (
|
|
|
28614
28934
|
// NetworkPolicyError carrying the policy's reason. A read is called or not depending
|
|
28615
28935
|
// on where the policy answers reads from. Answered from the store, a GET of a
|
|
28616
28936
|
// root resource completes with the row the store holds for it (see
|
|
28617
|
-
// findItemInStore)
|
|
28618
|
-
//
|
|
28619
|
-
//
|
|
28620
|
-
//
|
|
28937
|
+
// findItemInStore), and a root GET or GET_MANY holding rows for these params
|
|
28938
|
+
// (the kept page, drawn while the request is out) with those — handing them
|
|
28939
|
+
// back is an upsert without effect, so the action completes with what it had
|
|
28940
|
+
// and nothing is asked. A relationship GET has no row of its own to answer
|
|
28941
|
+
// with and settles with the same error. A completed GET asked to rerun never
|
|
28942
|
+
// gets here (actions.js holds it).
|
|
28621
28943
|
const applyNetworkPolicy = (
|
|
28622
28944
|
restCallback,
|
|
28623
28945
|
{ verb, isMany, findItemInStore },
|
|
@@ -28631,6 +28953,12 @@ const applyNetworkPolicy = (
|
|
|
28631
28953
|
if (!policy.readsFromStore) {
|
|
28632
28954
|
return restCallback(params, context);
|
|
28633
28955
|
}
|
|
28956
|
+
if (isKeepableRead(context.action)) {
|
|
28957
|
+
const dataHeld = context.action.dataSignal.peek();
|
|
28958
|
+
if (dataHeld !== undefined) {
|
|
28959
|
+
return dataHeld;
|
|
28960
|
+
}
|
|
28961
|
+
}
|
|
28634
28962
|
if (!isMany && findItemInStore) {
|
|
28635
28963
|
const item = findItemInStore(params, context.action);
|
|
28636
28964
|
if (item) {
|
|
@@ -29158,24 +29486,6 @@ const TYPE_CONVERTERS = {
|
|
|
29158
29486
|
},
|
|
29159
29487
|
};
|
|
29160
29488
|
|
|
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
29489
|
const promiseStateWeakMap = new WeakMap();
|
|
29180
29490
|
const usePromiseAsyncData = (
|
|
29181
29491
|
promise,
|
|
@@ -34076,11 +34386,17 @@ const useContentKeepingLeavingPages = (branch, readShownBranch, parentPage) => {
|
|
|
34076
34386
|
}
|
|
34077
34387
|
}
|
|
34078
34388
|
});
|
|
34389
|
+
// Also run when a Suspense boundary above parks this container — a page
|
|
34390
|
+
// arriving that suspends to a `<Loading>` around both pages: every hook of
|
|
34391
|
+
// the pages kept while leaving has then been cleaned up, which is all that
|
|
34392
|
+
// keeping them saved. Dropped, so the render resuming the container takes
|
|
34393
|
+
// them down, rather than re-running every effect of a hidden page.
|
|
34079
34394
|
useLayoutEffect(() => {
|
|
34080
34395
|
return () => {
|
|
34081
34396
|
for (const entry of leavingRef.current) {
|
|
34082
34397
|
entry.hold.containers.delete(rerender);
|
|
34083
34398
|
}
|
|
34399
|
+
leavingRef.current = [];
|
|
34084
34400
|
};
|
|
34085
34401
|
}, []);
|
|
34086
34402
|
if (leaving.length === 0) {
|
|
@@ -38892,6 +39208,37 @@ const writeCover = (edge, element, size) => {
|
|
|
38892
39208
|
style.setProperty(property, value);
|
|
38893
39209
|
};
|
|
38894
39210
|
|
|
39211
|
+
// Every route action, with whether it asks something of the address at the
|
|
39212
|
+
// moment: its route matches and its params getter returns something.
|
|
39213
|
+
const routeActionEntriesSignal = signal([]);
|
|
39214
|
+
let activeRouteActionsPrevious = new Set();
|
|
39215
|
+
/**
|
|
39216
|
+
* The route actions asking something for the page on screen: their route
|
|
39217
|
+
* matches and their params getter returns something — the running ones, not
|
|
39218
|
+
* every matching one, so an action bound to every page that asks only when a
|
|
39219
|
+
* popup is open over it counts while it is open. Read to rerun what the page
|
|
39220
|
+
* reads (the app back from the background), and by `keepPageOnScreen`.
|
|
39221
|
+
* The set is handed out again only when its members change.
|
|
39222
|
+
*
|
|
39223
|
+
* A page's code is declared as a route action too; `action.meta.verb` says
|
|
39224
|
+
* which ones read data.
|
|
39225
|
+
*
|
|
39226
|
+
* @type {import("@preact/signals").ReadonlySignal<Set<object>>}
|
|
39227
|
+
*/
|
|
39228
|
+
const activeRouteActionsSignal = computed(() => {
|
|
39229
|
+
const active = new Set();
|
|
39230
|
+
for (const { routeAction, askingSignal } of routeActionEntriesSignal.value) {
|
|
39231
|
+
if (askingSignal.value) {
|
|
39232
|
+
active.add(routeAction);
|
|
39233
|
+
}
|
|
39234
|
+
}
|
|
39235
|
+
if (haveSameMembers(active, activeRouteActionsPrevious)) {
|
|
39236
|
+
return activeRouteActionsPrevious;
|
|
39237
|
+
}
|
|
39238
|
+
activeRouteActionsPrevious = active;
|
|
39239
|
+
return active;
|
|
39240
|
+
});
|
|
39241
|
+
|
|
38895
39242
|
/**
|
|
38896
39243
|
* Binds an action to a route: it runs when the route matches, with the params
|
|
38897
39244
|
* the effect reads off the address, runs again on a `reload()`, and is aborted
|
|
@@ -38949,6 +39296,13 @@ const routeAction = (
|
|
|
38949
39296
|
return params;
|
|
38950
39297
|
};
|
|
38951
39298
|
const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
|
|
39299
|
+
routeActionEntriesSignal.value = [
|
|
39300
|
+
...routeActionEntriesSignal.peek(),
|
|
39301
|
+
{
|
|
39302
|
+
routeAction: actionBoundToRoute,
|
|
39303
|
+
askingSignal: computed(() => Boolean(readParamsAsked())),
|
|
39304
|
+
},
|
|
39305
|
+
];
|
|
38952
39306
|
for (const route of routes) {
|
|
38953
39307
|
// Asked of the route's params as they are, like the effect asks them.
|
|
38954
39308
|
registerRouteReload(route, () =>
|
|
@@ -39032,6 +39386,343 @@ const anyMatchingRouteSignal = (routes) => {
|
|
|
39032
39386
|
return anyMatchingSignal;
|
|
39033
39387
|
};
|
|
39034
39388
|
|
|
39389
|
+
const haveSameMembers = (setA, setB) => {
|
|
39390
|
+
if (setA.size !== setB.size) {
|
|
39391
|
+
return false;
|
|
39392
|
+
}
|
|
39393
|
+
for (const value of setA) {
|
|
39394
|
+
if (!setB.has(value)) {
|
|
39395
|
+
return false;
|
|
39396
|
+
}
|
|
39397
|
+
}
|
|
39398
|
+
return true;
|
|
39399
|
+
};
|
|
39400
|
+
|
|
39401
|
+
/*
|
|
39402
|
+
* The page on screen, kept for the next document: what its reads answered,
|
|
39403
|
+
* written into a signal the app hands over, and drawn again by the same reads
|
|
39404
|
+
* in a document opening on the same page (a reload, a tab the system
|
|
39405
|
+
* discarded). The read side is state/rest/kept_reads.js; this is what decides
|
|
39406
|
+
* which reads are the page, and when the slot is written.
|
|
39407
|
+
*
|
|
39408
|
+
* The unit is the page, not the resource. One resource is read by several
|
|
39409
|
+
* screens — a search, someone's games, a game opened from anywhere — and kept
|
|
39410
|
+
* per resource it would keep every row ever opened. The page's reads are what
|
|
39411
|
+
* navi already runs for it: the route actions asking something for the page
|
|
39412
|
+
* (activeRouteActionsSignal) and the compositions the lists on screen read
|
|
39413
|
+
* (shownRangeReadersSignal). A search typed on the page is neither, and is not
|
|
39414
|
+
* kept.
|
|
39415
|
+
*
|
|
39416
|
+
* A read is written once it has answered in this document. Before that it
|
|
39417
|
+
* keeps what the previous document kept for it, never what it shows meanwhile:
|
|
39418
|
+
* an answer handed over from other params (a list standing in for the next
|
|
39419
|
+
* one) says nothing about these.
|
|
39420
|
+
*
|
|
39421
|
+
* The write waits for the page to settle and is flushed when the document is
|
|
39422
|
+
* hidden. A navigation goes through pages half there — the route matched, its
|
|
39423
|
+
* list not mounted yet, the page left still drawn under a transition — and
|
|
39424
|
+
* each would otherwise be written out in full. Hidden is the moment the system
|
|
39425
|
+
* may discard the document, so nothing is lost by waiting until then.
|
|
39426
|
+
*
|
|
39427
|
+
* The signal turning `undefined` from outside (sign-out) forgets the page, and
|
|
39428
|
+
* nothing is written until a read lands again: written at once, the page still
|
|
39429
|
+
* on screen would put back the account just left.
|
|
39430
|
+
*/
|
|
39431
|
+
|
|
39432
|
+
const WRITE_DELAY = 1000;
|
|
39433
|
+
const NOTHING_PENDING = {};
|
|
39434
|
+
let stopKeepingCurrent = null;
|
|
39435
|
+
|
|
39436
|
+
/**
|
|
39437
|
+
* Keeps what the page on screen reads, so that a new document opening on it —
|
|
39438
|
+
* a reload, a pull-to-refresh, a tab the system discarded in the background —
|
|
39439
|
+
* draws it again while its reads go out, rather than skeletons.
|
|
39440
|
+
*
|
|
39441
|
+
* What the page reads is what navi runs for it: the route actions asking
|
|
39442
|
+
* something for it (see `activeRouteActionsSignal`) whose action is a resource
|
|
39443
|
+
* `GET` or `GET_MANY`, and the `GET_RANGE` compositions the `<List.Items>` on
|
|
39444
|
+
* screen read, around the window they draw. A navigation replaces the slot with
|
|
39445
|
+
* the next page's reads, and the page left is dropped. In the next document,
|
|
39446
|
+
* the first run of the same read with the same params draws the kept answer as
|
|
39447
|
+
* its provisional value: `data` set while `loading` is `true`, the answer
|
|
39448
|
+
* replacing it. Under a network policy answering reads from the store, the
|
|
39449
|
+
* kept answer answers the read. The copy follows the store: a `PUT` on a row
|
|
39450
|
+
* rewrites it.
|
|
39451
|
+
*
|
|
39452
|
+
* Call it before the routes start: the first runs are the ones that look.
|
|
39453
|
+
*
|
|
39454
|
+
* @param {object} options
|
|
39455
|
+
* @param {import("@preact/signals").Signal} options.signal - where the slot is
|
|
39456
|
+
* written: a `stateSignal` with `persists: true` and `type: "object"`, its
|
|
39457
|
+
* `id` holding whatever makes a kept page unusable (the deployed version).
|
|
39458
|
+
* Writing `undefined` into it (sign-out) forgets the page, and nothing is
|
|
39459
|
+
* written again until a read lands.
|
|
39460
|
+
* @param {() => boolean} [options.when] - read whenever the slot is about to be
|
|
39461
|
+
* read or written: `false` reads nothing, writes nothing, and empties the
|
|
39462
|
+
* signal (a tab viewing as someone else). A function, so it can read a signal.
|
|
39463
|
+
* @param {object[]} [options.always] - routes whose reads stay kept once the
|
|
39464
|
+
* page is left, until it is visited again: the page an app opens on when it
|
|
39465
|
+
* is launched rather than reloaded.
|
|
39466
|
+
* @returns {() => void} stops keeping the page.
|
|
39467
|
+
* @see docs/resource.md — what is kept and what is not, what stays the app's
|
|
39468
|
+
*/
|
|
39469
|
+
const keepPageOnScreen = ({
|
|
39470
|
+
signal: keptSignal,
|
|
39471
|
+
when,
|
|
39472
|
+
always = [],
|
|
39473
|
+
} = {}) => {
|
|
39474
|
+
if (!isSignal(keptSignal)) {
|
|
39475
|
+
throw new TypeError(
|
|
39476
|
+
`keepPageOnScreen() needs a signal to write the page into, received ${keptSignal}`,
|
|
39477
|
+
);
|
|
39478
|
+
}
|
|
39479
|
+
if (stopKeepingCurrent) {
|
|
39480
|
+
stopKeepingCurrent();
|
|
39481
|
+
}
|
|
39482
|
+
const isAllowed = () => {
|
|
39483
|
+
return when ? Boolean(when()) : true;
|
|
39484
|
+
};
|
|
39485
|
+
const alwaysMatchingSignal = anyMatchingRouteSignal(always);
|
|
39486
|
+
|
|
39487
|
+
// What the signal held when it was read: the previous document's page.
|
|
39488
|
+
let loadedEntryMap = new Map();
|
|
39489
|
+
let loadedAlwaysKeys = [];
|
|
39490
|
+
// Which reads answered in this document (since the last forget): only
|
|
39491
|
+
// those write what they show.
|
|
39492
|
+
let answeredActionWeakSet = new WeakSet();
|
|
39493
|
+
let answeredRangeKeySet = new Set();
|
|
39494
|
+
// The state each action was last seen in, and the last answer seen of each
|
|
39495
|
+
// range reader: a read lands when they move, and not because the slot looks
|
|
39496
|
+
// again at something that had answered before a forget.
|
|
39497
|
+
const stateSeenWeakMap = new WeakMap();
|
|
39498
|
+
const answeredAtSeenWeakMap = new WeakMap();
|
|
39499
|
+
// Forgotten: nothing is written until a read lands.
|
|
39500
|
+
let paused = false;
|
|
39501
|
+
// The reads of the always-kept page, as they were the last time it was on
|
|
39502
|
+
// screen in this document; null until then.
|
|
39503
|
+
let alwaysReads = null;
|
|
39504
|
+
|
|
39505
|
+
const forget = () => {
|
|
39506
|
+
loadedEntryMap = new Map();
|
|
39507
|
+
loadedAlwaysKeys = [];
|
|
39508
|
+
answeredActionWeakSet = new WeakSet();
|
|
39509
|
+
answeredRangeKeySet = new Set();
|
|
39510
|
+
alwaysReads = null;
|
|
39511
|
+
paused = true;
|
|
39512
|
+
withdrawKeptReads();
|
|
39513
|
+
};
|
|
39514
|
+
|
|
39515
|
+
let lastWritten;
|
|
39516
|
+
let pendingWrite = NOTHING_PENDING;
|
|
39517
|
+
let writeTimeout;
|
|
39518
|
+
const flush = () => {
|
|
39519
|
+
clearTimeout(writeTimeout);
|
|
39520
|
+
if (pendingWrite === NOTHING_PENDING) {
|
|
39521
|
+
return;
|
|
39522
|
+
}
|
|
39523
|
+
const value = pendingWrite;
|
|
39524
|
+
pendingWrite = NOTHING_PENDING;
|
|
39525
|
+
if (compareTwoJsValues(value, lastWritten)) {
|
|
39526
|
+
return;
|
|
39527
|
+
}
|
|
39528
|
+
lastWritten = value;
|
|
39529
|
+
keptSignal.value = value;
|
|
39530
|
+
};
|
|
39531
|
+
const scheduleWrite = (value) => {
|
|
39532
|
+
pendingWrite = value;
|
|
39533
|
+
clearTimeout(writeTimeout);
|
|
39534
|
+
writeTimeout = setTimeout(flush, WRITE_DELAY);
|
|
39535
|
+
};
|
|
39536
|
+
const onVisibilityChange = () => {
|
|
39537
|
+
if (document.visibilityState === "hidden") {
|
|
39538
|
+
flush();
|
|
39539
|
+
}
|
|
39540
|
+
};
|
|
39541
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
39542
|
+
window.addEventListener("pagehide", flush);
|
|
39543
|
+
|
|
39544
|
+
// Runs at once with what the signal already holds (the slot read from
|
|
39545
|
+
// storage), then on every write — the slot's own ones are recognized and
|
|
39546
|
+
// left alone.
|
|
39547
|
+
const unsubscribe = keptSignal.subscribe((value) => {
|
|
39548
|
+
if (value === lastWritten) {
|
|
39549
|
+
return;
|
|
39550
|
+
}
|
|
39551
|
+
lastWritten = value;
|
|
39552
|
+
pendingWrite = NOTHING_PENDING;
|
|
39553
|
+
clearTimeout(writeTimeout);
|
|
39554
|
+
if (
|
|
39555
|
+
value === undefined ||
|
|
39556
|
+
value === null ||
|
|
39557
|
+
typeof value !== "object" ||
|
|
39558
|
+
!isAllowed()
|
|
39559
|
+
) {
|
|
39560
|
+
forget();
|
|
39561
|
+
return;
|
|
39562
|
+
}
|
|
39563
|
+
const entries =
|
|
39564
|
+
value.entries && typeof value.entries === "object" ? value.entries : {};
|
|
39565
|
+
loadedEntryMap = new Map(Object.entries(entries));
|
|
39566
|
+
loadedAlwaysKeys = Array.isArray(value.always)
|
|
39567
|
+
? value.always.filter((key) => loadedEntryMap.has(key))
|
|
39568
|
+
: [];
|
|
39569
|
+
alwaysReads = null;
|
|
39570
|
+
paused = false;
|
|
39571
|
+
offerKeptReads(loadedEntryMap);
|
|
39572
|
+
});
|
|
39573
|
+
|
|
39574
|
+
const actionReadWeakMap = new WeakMap();
|
|
39575
|
+
const actionReadOf = (action) => {
|
|
39576
|
+
let read = actionReadWeakMap.get(action);
|
|
39577
|
+
if (read) {
|
|
39578
|
+
return read;
|
|
39579
|
+
}
|
|
39580
|
+
read = () => {
|
|
39581
|
+
const state = action.runningStateSignal.value;
|
|
39582
|
+
const stateSeen = stateSeenWeakMap.get(action);
|
|
39583
|
+
stateSeenWeakMap.set(action, state);
|
|
39584
|
+
const landed = state === COMPLETED && stateSeen !== COMPLETED;
|
|
39585
|
+
if (landed) {
|
|
39586
|
+
answeredActionWeakSet.add(action);
|
|
39587
|
+
}
|
|
39588
|
+
return {
|
|
39589
|
+
key: getKeptReadKey(action.name, action.params),
|
|
39590
|
+
answered: answeredActionWeakSet.has(action),
|
|
39591
|
+
landed,
|
|
39592
|
+
readEntry: () => {
|
|
39593
|
+
const data = action.dataSignal.value;
|
|
39594
|
+
if (data === undefined || data === null) {
|
|
39595
|
+
return undefined;
|
|
39596
|
+
}
|
|
39597
|
+
return serializeData(data);
|
|
39598
|
+
},
|
|
39599
|
+
};
|
|
39600
|
+
};
|
|
39601
|
+
actionReadWeakMap.set(action, read);
|
|
39602
|
+
return read;
|
|
39603
|
+
};
|
|
39604
|
+
const rangeReadWeakMap = new WeakMap();
|
|
39605
|
+
const rangeReadOf = (reader) => {
|
|
39606
|
+
let read = rangeReadWeakMap.get(reader);
|
|
39607
|
+
if (read) {
|
|
39608
|
+
return read;
|
|
39609
|
+
}
|
|
39610
|
+
read = () => {
|
|
39611
|
+
const { key, answeredAt, readEntry } = reader.readKept();
|
|
39612
|
+
const answeredAtSeen = answeredAtSeenWeakMap.get(reader) || 0;
|
|
39613
|
+
answeredAtSeenWeakMap.set(reader, answeredAt);
|
|
39614
|
+
const landed = answeredAt > answeredAtSeen;
|
|
39615
|
+
if (landed) {
|
|
39616
|
+
answeredRangeKeySet.add(key);
|
|
39617
|
+
}
|
|
39618
|
+
return {
|
|
39619
|
+
key,
|
|
39620
|
+
answered: answeredRangeKeySet.has(key),
|
|
39621
|
+
landed,
|
|
39622
|
+
readEntry,
|
|
39623
|
+
};
|
|
39624
|
+
};
|
|
39625
|
+
rangeReadWeakMap.set(reader, read);
|
|
39626
|
+
return read;
|
|
39627
|
+
};
|
|
39628
|
+
const readsOnScreen = () => {
|
|
39629
|
+
const reads = [];
|
|
39630
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
39631
|
+
// The instance is what is kept, so that a page left keeps what it read.
|
|
39632
|
+
// The binding's callSource names that instance and is rewritten once the
|
|
39633
|
+
// binding has moved to another one: read, it brings the slot along.
|
|
39634
|
+
// eslint-disable-next-line no-unused-expressions
|
|
39635
|
+
routeAction.callSource;
|
|
39636
|
+
const action = getActionPrivateProperties(routeAction).currentAction;
|
|
39637
|
+
if (action.params === NO_PARAMS || !isKeepableRead(action)) {
|
|
39638
|
+
continue;
|
|
39639
|
+
}
|
|
39640
|
+
reads.push(actionReadOf(action));
|
|
39641
|
+
}
|
|
39642
|
+
for (const reader of shownRangeReadersSignal.value.keys()) {
|
|
39643
|
+
reads.push(rangeReadOf(reader));
|
|
39644
|
+
}
|
|
39645
|
+
return reads;
|
|
39646
|
+
};
|
|
39647
|
+
|
|
39648
|
+
const stopEffect = effect(() => {
|
|
39649
|
+
const allowed = isAllowed();
|
|
39650
|
+
const routed = routedSignal.value;
|
|
39651
|
+
const alwaysMatching = alwaysMatchingSignal.value;
|
|
39652
|
+
if (!allowed) {
|
|
39653
|
+
forget();
|
|
39654
|
+
pendingWrite = undefined;
|
|
39655
|
+
flush();
|
|
39656
|
+
return;
|
|
39657
|
+
}
|
|
39658
|
+
if (!routed) {
|
|
39659
|
+
// No page yet: the slot is what the first reads will draw.
|
|
39660
|
+
return;
|
|
39661
|
+
}
|
|
39662
|
+
const screenReads = readsOnScreen();
|
|
39663
|
+
if (alwaysMatching) {
|
|
39664
|
+
alwaysReads = screenReads;
|
|
39665
|
+
}
|
|
39666
|
+
const entries = {};
|
|
39667
|
+
let landed = false;
|
|
39668
|
+
const collect = (reads) => {
|
|
39669
|
+
const keys = [];
|
|
39670
|
+
for (const read of reads) {
|
|
39671
|
+
const result = read();
|
|
39672
|
+
if (result.landed) {
|
|
39673
|
+
landed = true;
|
|
39674
|
+
}
|
|
39675
|
+
const entry = result.answered
|
|
39676
|
+
? result.readEntry()
|
|
39677
|
+
: loadedEntryMap.get(result.key);
|
|
39678
|
+
if (entry === undefined) {
|
|
39679
|
+
continue;
|
|
39680
|
+
}
|
|
39681
|
+
entries[result.key] = entry;
|
|
39682
|
+
keys.push(result.key);
|
|
39683
|
+
}
|
|
39684
|
+
return keys;
|
|
39685
|
+
};
|
|
39686
|
+
const screenKeys = collect(screenReads);
|
|
39687
|
+
let alwaysKeys;
|
|
39688
|
+
if (alwaysReads === screenReads) {
|
|
39689
|
+
alwaysKeys = screenKeys;
|
|
39690
|
+
} else if (alwaysReads) {
|
|
39691
|
+
alwaysKeys = collect(alwaysReads);
|
|
39692
|
+
} else {
|
|
39693
|
+
alwaysKeys = loadedAlwaysKeys;
|
|
39694
|
+
for (const key of alwaysKeys) {
|
|
39695
|
+
entries[key] = loadedEntryMap.get(key);
|
|
39696
|
+
}
|
|
39697
|
+
}
|
|
39698
|
+
if (paused) {
|
|
39699
|
+
if (!landed) {
|
|
39700
|
+
return;
|
|
39701
|
+
}
|
|
39702
|
+
paused = false;
|
|
39703
|
+
}
|
|
39704
|
+
if (screenKeys.length === 0 && alwaysKeys.length === 0) {
|
|
39705
|
+
scheduleWrite(undefined);
|
|
39706
|
+
return;
|
|
39707
|
+
}
|
|
39708
|
+
scheduleWrite({ entries, screen: screenKeys, always: alwaysKeys });
|
|
39709
|
+
});
|
|
39710
|
+
|
|
39711
|
+
const stop = () => {
|
|
39712
|
+
flush();
|
|
39713
|
+
stopEffect();
|
|
39714
|
+
unsubscribe();
|
|
39715
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
39716
|
+
window.removeEventListener("pagehide", flush);
|
|
39717
|
+
withdrawKeptReads();
|
|
39718
|
+
if (stopKeepingCurrent === stop) {
|
|
39719
|
+
stopKeepingCurrent = null;
|
|
39720
|
+
}
|
|
39721
|
+
};
|
|
39722
|
+
stopKeepingCurrent = stop;
|
|
39723
|
+
return stop;
|
|
39724
|
+
};
|
|
39725
|
+
|
|
39035
39726
|
/**
|
|
39036
39727
|
* A popup that is the anchor, brought to the front.
|
|
39037
39728
|
*
|
|
@@ -66394,7 +67085,7 @@ const css$E = /* css */`
|
|
|
66394
67085
|
/* A plain div, unlike dialogEl itself (a real <dialog>, natively hidden
|
|
66395
67086
|
by default until .show()/.showModal() adds [open]) — needs its own
|
|
66396
67087
|
starting-hidden mechanism. [navi-hidden] is set from useDialogProps'
|
|
66397
|
-
own backdropProps (recomputed from openController.
|
|
67088
|
+
own backdropProps (recomputed from openController.openedInDom on every
|
|
66398
67089
|
render, present from the very first one), then toggled by plain
|
|
66399
67090
|
removeAttribute/setAttribute in openEffect/close, never an explicit
|
|
66400
67091
|
display override — removing the attribute just lets this rule stop
|
|
@@ -68054,7 +68745,7 @@ const useDialogProps = props => {
|
|
|
68054
68745
|
"navi-out-of-flow": "",
|
|
68055
68746
|
"baseClassName": "navi_dialog_backdrop",
|
|
68056
68747
|
"aria-hidden": "true",
|
|
68057
|
-
// Recomputed fresh on every render from openController.
|
|
68748
|
+
// Recomputed fresh on every render from openController.openedInDom (not
|
|
68058
68749
|
// driven through a mount-time layout effect, unlike this file's own
|
|
68059
68750
|
// imperative open/close toggling below) — present in the DOM
|
|
68060
68751
|
// synchronously from the very first commit, matching this file's own
|
|
@@ -68064,7 +68755,7 @@ const useDialogProps = props => {
|
|
|
68064
68755
|
// needs — see popover.jsx's own identical prop for the full reasoning.
|
|
68065
68756
|
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
68066
68757
|
// Present from this very first render (recomputed fresh on every one
|
|
68067
|
-
// from openController.
|
|
68758
|
+
// from openController.openedInDom, not a frozen mount-time constant) so
|
|
68068
68759
|
// there's no gap for the browser to ever paint this plain-div backdrop
|
|
68069
68760
|
// visible before anything has actually opened it — see popover.jsx's
|
|
68070
68761
|
// own identical prop for the full reasoning, and this file's own CSS
|
|
@@ -68084,7 +68775,7 @@ const useDialogProps = props => {
|
|
|
68084
68775
|
// (kept once, not repeated here).
|
|
68085
68776
|
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
68086
68777
|
// Present from the very first render (recomputed fresh from
|
|
68087
|
-
// openController.
|
|
68778
|
+
// openController.openedInDom every time, not a frozen mount-time constant —
|
|
68088
68779
|
// see popover.jsx's own identical prop for the full reasoning) so a
|
|
68089
68780
|
// consumer whose own CSS also sets display (e.g. Popup's flex prop)
|
|
68090
68781
|
// can't silently defeat showModal()/close()'s native open/close — see
|
|
@@ -68487,7 +69178,7 @@ const css$D = /* css */`
|
|
|
68487
69178
|
without this it would flash visible for one frame on mount, before
|
|
68488
69179
|
openEffect's own JS ever gets a chance to hide it. [navi-hidden] is
|
|
68489
69180
|
set from usePopoverProps' own contentProps (recomputed from
|
|
68490
|
-
openController.
|
|
69181
|
+
openController.openedInDom on every render, present from the very first
|
|
68491
69182
|
one — see there for why), then toggled by plain
|
|
68492
69183
|
removeAttribute/setAttribute in openEffect/close, never an explicit
|
|
68493
69184
|
display override: removing the attribute just lets this rule stop
|
|
@@ -69852,7 +70543,7 @@ const usePopoverProps = props => {
|
|
|
69852
70543
|
"navi-out-of-flow": "",
|
|
69853
70544
|
"baseClassName": "navi_popover_backdrop",
|
|
69854
70545
|
"aria-hidden": "true",
|
|
69855
|
-
// Recomputed fresh on every render from openController.
|
|
70546
|
+
// Recomputed fresh on every render from openController.openedInDom (not a
|
|
69856
70547
|
// frozen mount-time constant, same pattern as navi-hidden just below)
|
|
69857
70548
|
// rather than driven through a mount-time layout effect — its own
|
|
69858
70549
|
// actual open/close toggling is still done entirely imperatively
|
|
@@ -69927,7 +70618,7 @@ const usePopoverProps = props => {
|
|
|
69927
70618
|
// starting-hidden default) — present from this very first render so
|
|
69928
70619
|
// there's no gap for the browser to ever paint it visible before
|
|
69929
70620
|
// anything has actually opened it. Recomputed fresh on every render from
|
|
69930
|
-
// openController.
|
|
70621
|
+
// openController.openedInDom (not a frozen mount-time constant) — Preact
|
|
69931
70622
|
// only touches the DOM for a prop whose value actually changed since
|
|
69932
70623
|
// the last render, so as long as this always reflects the *current*
|
|
69933
70624
|
// truth, it never fights the imperative
|
|
@@ -74367,7 +75058,6 @@ const ListUI = props => {
|
|
|
74367
75058
|
// What the runs ask for and stand for, in items: a source paginates in items,
|
|
74368
75059
|
// and asks for the first picture's items and the rest in one round trip.
|
|
74369
75060
|
listItems.pageSize = PAGE_SIZE_DEFAULT;
|
|
74370
|
-
listItems.scrolled = scrolled ?? defaultScrolled;
|
|
74371
75061
|
const {
|
|
74372
75062
|
virtualItemSizeSignal,
|
|
74373
75063
|
renderWindow,
|
|
@@ -74746,6 +75436,45 @@ const useListScrollSync = ({
|
|
|
74746
75436
|
columns
|
|
74747
75437
|
}) => {
|
|
74748
75438
|
const debugScroll = useDebugScroll();
|
|
75439
|
+
// Where the list must be: what the caller holds it at (`scrolled`), or where
|
|
75440
|
+
// it opens and then lets go (`defaultScrolled`). A `scrolled` that changes is
|
|
75441
|
+
// the caller moving the list, so the hold is armed again — that is what makes
|
|
75442
|
+
// it controlled. A `defaultScrolled` that changes is where the list opens,
|
|
75443
|
+
// read again from something that answered again (a count fresher than the
|
|
75444
|
+
// one kept from the previous visit): followed for as long as nobody has
|
|
75445
|
+
// moved the list, and compared by value, since it is said at every render.
|
|
75446
|
+
// Nothing to hold it at (a position not saved yet) is not a position: the
|
|
75447
|
+
// list opens where it opens.
|
|
75448
|
+
const startPlaceRef = useRef(null);
|
|
75449
|
+
if (startPlaceRef.current === null) {
|
|
75450
|
+
startPlaceRef.current = {
|
|
75451
|
+
userTookOver: false,
|
|
75452
|
+
wanted: scrolled ?? defaultScrolled,
|
|
75453
|
+
scrolled,
|
|
75454
|
+
defaultScrolled,
|
|
75455
|
+
way: null,
|
|
75456
|
+
arrived: false
|
|
75457
|
+
};
|
|
75458
|
+
} else {
|
|
75459
|
+
const place = startPlaceRef.current;
|
|
75460
|
+
if (place.scrolled !== scrolled) {
|
|
75461
|
+
place.scrolled = scrolled;
|
|
75462
|
+
place.defaultScrolled = defaultScrolled;
|
|
75463
|
+
place.wanted = scrolled ?? defaultScrolled;
|
|
75464
|
+
place.userTookOver = false;
|
|
75465
|
+
} else if ((scrolled === undefined || scrolled === null) && !compareTwoJsValues(place.defaultScrolled, defaultScrolled)) {
|
|
75466
|
+
place.defaultScrolled = defaultScrolled;
|
|
75467
|
+
if (!place.userTookOver) {
|
|
75468
|
+
place.wanted = defaultScrolled;
|
|
75469
|
+
// Placed again from where it stands: the way to the previous place
|
|
75470
|
+
// says nothing about whose the next scrolls are.
|
|
75471
|
+
place.way = null;
|
|
75472
|
+
place.arrived = false;
|
|
75473
|
+
}
|
|
75474
|
+
}
|
|
75475
|
+
}
|
|
75476
|
+
const scrolledWanted = startPlaceRef.current.wanted;
|
|
75477
|
+
listItems.scrolled = scrolledWanted;
|
|
74749
75478
|
// The items drawn, [start, end) among the list's own. A ref as well as a
|
|
74750
75479
|
// state: the render moves it where the list is held (holdWindow) without a
|
|
74751
75480
|
// commit of its own.
|
|
@@ -74760,7 +75489,7 @@ const useListScrollSync = ({
|
|
|
74760
75489
|
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal, {
|
|
74761
75490
|
inLines: Boolean(columns),
|
|
74762
75491
|
windowLeavesItemsOut,
|
|
74763
|
-
scrolledWanted
|
|
75492
|
+
scrolledWanted,
|
|
74764
75493
|
// The fillers hold the room of items above the screen at this size: a
|
|
74765
75494
|
// size that changes moves what is on screen, like items landing above it.
|
|
74766
75495
|
beforeSizeChange: () => listItems.captureAnchor()
|
|
@@ -75093,25 +75822,7 @@ const useListScrollSync = ({
|
|
|
75093
75822
|
const newEnd = newStart + windowSize;
|
|
75094
75823
|
updateRenderWindow(newStart, newEnd, `item to scroll (at ${index}) is out of render window`);
|
|
75095
75824
|
};
|
|
75096
|
-
|
|
75097
|
-
// Where the list must be: what the caller holds it at (`scrolled`), or where
|
|
75098
|
-
// it opens and then lets go (`defaultScrolled`). A `scrolled` that changes is
|
|
75099
|
-
// the caller moving the list, so the hold is armed again — that is what makes
|
|
75100
|
-
// it controlled.
|
|
75101
|
-
// Nothing to hold it at (a position not saved yet) is not a position: the
|
|
75102
|
-
// list opens where it opens.
|
|
75103
|
-
const scrolledWanted = scrolled ?? defaultScrolled;
|
|
75104
75825
|
const scrolledFallback = scrolled === undefined || scrolled === null ? "start" : defaultScrolled ?? "start";
|
|
75105
|
-
const startPlaceRef = useRef({
|
|
75106
|
-
userTookOver: false,
|
|
75107
|
-
wanted: scrolledWanted,
|
|
75108
|
-
way: null,
|
|
75109
|
-
arrived: false
|
|
75110
|
-
});
|
|
75111
|
-
if (startPlaceRef.current.wanted !== scrolledWanted) {
|
|
75112
|
-
startPlaceRef.current.wanted = scrolledWanted;
|
|
75113
|
-
startPlaceRef.current.userTookOver = false;
|
|
75114
|
-
}
|
|
75115
75826
|
// Only one thing owns the scroll at a time: while the list is holding itself
|
|
75116
75827
|
// somewhere, the anchoring stays out of it (holding an item still is precisely
|
|
75117
75828
|
// not being at the end anymore once what is above it shrinks).
|
|
@@ -78939,6 +79650,17 @@ const useItemStore = ({
|
|
|
78939
79650
|
}
|
|
78940
79651
|
const pages = pagesRef.current;
|
|
78941
79652
|
const [, setPageVersion] = useState(0);
|
|
79653
|
+
// A list on screen is part of what the page reads: the composition it reads
|
|
79654
|
+
// is kept with the page for the next document (see keepPageOnScreen).
|
|
79655
|
+
useLayoutEffect(() => {
|
|
79656
|
+
if (inMemory || typeof itemsAction !== "function") {
|
|
79657
|
+
return undefined;
|
|
79658
|
+
}
|
|
79659
|
+
if (!itemsAction.isRangeReader) {
|
|
79660
|
+
return undefined;
|
|
79661
|
+
}
|
|
79662
|
+
return showRangeReader(itemsAction);
|
|
79663
|
+
}, [inMemory, itemsAction]);
|
|
78942
79664
|
// The ranks whose item is from before: the run came back to the screen, or
|
|
78943
79665
|
// the source said the collection moved. They stay drawn until a page confirms
|
|
78944
79666
|
// or replaces them, rank by rank. A page covers what the window framed when it
|
|
@@ -79072,6 +79794,7 @@ const useItemStore = ({
|
|
|
79072
79794
|
}
|
|
79073
79795
|
}
|
|
79074
79796
|
if (typeof itemsAction === "function" && itemsAction.trimComposition) {
|
|
79797
|
+
itemsAction.frameComposition(windowFrom, windowTo);
|
|
79075
79798
|
itemsAction.trimComposition(keepFrom, keepTo, budget);
|
|
79076
79799
|
}
|
|
79077
79800
|
},
|
|
@@ -79641,6 +80364,9 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
79641
80364
|
* room has to be restated as a number by whoever asks. The `index` and
|
|
79642
80365
|
* `visibleCount` it also hands out say where to aim before the item is found,
|
|
79643
80366
|
* and how many items to draw before the first paint (see `renderBudget`).
|
|
80367
|
+
* A value that changes before anyone has moved the list is followed (a place
|
|
80368
|
+
* read from a count that answered again): it is compared by value, so it can
|
|
80369
|
+
* be computed at every render.
|
|
79644
80370
|
* @param {"start"|"end"|number|{id: string, index?: number, offset?: number, visibleCount?: number}} [props.scrolled]
|
|
79645
80371
|
* The same, but held: the list goes back there every time this changes, even
|
|
79646
80372
|
* after the user has scrolled — the caller owns where the list is (see
|
|
@@ -83172,6 +83898,8 @@ const TRAVELS_NONE = [];
|
|
|
83172
83898
|
* name?: string,
|
|
83173
83899
|
* min?: any,
|
|
83174
83900
|
* max?: any,
|
|
83901
|
+
* minMessage?: any,
|
|
83902
|
+
* maxMessage?: any,
|
|
83175
83903
|
* step?: number,
|
|
83176
83904
|
* type?: string,
|
|
83177
83905
|
* editable?: boolean,
|
|
@@ -83210,7 +83938,15 @@ const TRAVELS_NONE = [];
|
|
|
83210
83938
|
* @param {number} [step=1] How many steps a press covers.
|
|
83211
83939
|
* @param {number} [duration=250] How long a travel takes, in milliseconds.
|
|
83212
83940
|
* @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
|
|
83941
|
+
* them the travel simply does not happen and the chevron that way says so,
|
|
83942
|
+
* in the words the control in the middle uses for a value past that end ("La
|
|
83943
|
+
* date doit être au plus tard le 2 avril 2027", "Max 5"). A value the control
|
|
83944
|
+
* has no such sentence for — what a plain `Spin` steps through — is "nothing
|
|
83945
|
+
* after this one".
|
|
83946
|
+
* @param {any} [maxMessage] What is said at `max` instead: by the chevron
|
|
83947
|
+
* refused there, by a travel asked past it, and by the control in the middle
|
|
83948
|
+
* for a value past it (picked in the calendar, typed). `minMessage` is its
|
|
83949
|
+
* twin at `min`.
|
|
83214
83950
|
* @param {string} [padding] The room around the value, `paddingX`/`paddingY`
|
|
83215
83951
|
* and the four sides included. It goes on what is inside the box rather than
|
|
83216
83952
|
* on the box: above and below it is taken by the value AND by the two
|
|
@@ -83240,6 +83976,8 @@ const Spin = ({
|
|
|
83240
83976
|
name,
|
|
83241
83977
|
min,
|
|
83242
83978
|
max,
|
|
83979
|
+
minMessage,
|
|
83980
|
+
maxMessage,
|
|
83243
83981
|
step = 1,
|
|
83244
83982
|
duration = 250,
|
|
83245
83983
|
type = "text",
|
|
@@ -83365,15 +84103,31 @@ const Spin = ({
|
|
|
83365
84103
|
const readOnlyResolved = Boolean(readOnly || readOnlyFromAbove || loadingResolved);
|
|
83366
84104
|
const disabledResolved = Boolean(disabled || disabledFromAbove);
|
|
83367
84105
|
|
|
84106
|
+
// The end of what one may reach, said the way the control in the middle
|
|
84107
|
+
// says it about a value past that end — its `minMessage`/`maxMessage`, or the
|
|
84108
|
+
// sentence its type has for the bound — so a chevron, the calendar and a
|
|
84109
|
+
// typed value refuse in the same words. Asked of the control at the press:
|
|
84110
|
+
// it is not there yet on the first render. `goingNext` is towards the bigger
|
|
84111
|
+
// values, whichever end of the box that is.
|
|
84112
|
+
const boundMessage = goingNext => {
|
|
84113
|
+
const constraint = goingNext ? MAX_CONSTRAINT : MIN_CONSTRAINT;
|
|
84114
|
+
const controlEl = controlRef.current;
|
|
84115
|
+
const controller = (findControlHost(controlEl) || controlEl).__uiStateController__;
|
|
84116
|
+
const {
|
|
84117
|
+
message
|
|
84118
|
+
} = getConstraintMessage(controller, constraint, constraint.messageAtBound(controller), {});
|
|
84119
|
+
return message ?? naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before");
|
|
84120
|
+
};
|
|
84121
|
+
|
|
83368
84122
|
// Why a chevron refuses, in its own words — and only when the reason is
|
|
83369
84123
|
// ours: the end of what one may reach is something this control knows and
|
|
83370
84124
|
// navi cannot guess. For everything else the reason belongs to the state the
|
|
83371
84125
|
// whole control is in, and it is said about the control ("read-only",
|
|
83372
84126
|
// "busy") rather than about the button, which is not what the user was
|
|
83373
84127
|
// pressing.
|
|
83374
|
-
const wayOutMessage = (allowed,
|
|
84128
|
+
const wayOutMessage = (allowed, isNext) => {
|
|
83375
84129
|
if (!allowed) {
|
|
83376
|
-
return
|
|
84130
|
+
return boundMessage(isNext);
|
|
83377
84131
|
}
|
|
83378
84132
|
if (loadingResolved) {
|
|
83379
84133
|
return naviI18n("constraint.busy.default");
|
|
@@ -83485,7 +84239,7 @@ const Spin = ({
|
|
|
83485
84239
|
// answer belongs.
|
|
83486
84240
|
e.preventDefault();
|
|
83487
84241
|
e.stopPropagation();
|
|
83488
|
-
openCallout(
|
|
84242
|
+
openCallout(boundMessage(goingNext), {
|
|
83489
84243
|
anchorElement: middleRef.current,
|
|
83490
84244
|
status: "info",
|
|
83491
84245
|
openingEvent: e.detail.event ?? e
|
|
@@ -83544,12 +84298,14 @@ const Spin = ({
|
|
|
83544
84298
|
const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
|
|
83545
84299
|
const wayOut = atStart => {
|
|
83546
84300
|
const isNext = atStart ? startIsNext : !startIsNext;
|
|
84301
|
+
const allowed = atStart ? startAllowed : endAllowed;
|
|
83547
84302
|
return jsx(WayOut, {
|
|
83548
84303
|
atStart: atStart,
|
|
83549
84304
|
onPointerDown: e => {
|
|
83550
84305
|
wayOutPointerTypeRef.current = e.pointerType;
|
|
83551
84306
|
},
|
|
83552
|
-
|
|
84307
|
+
unavailable: !allowed || loadingResolved || readOnlyResolved,
|
|
84308
|
+
unavailableMessage: () => wayOutMessage(allowed, isNext),
|
|
83553
84309
|
label: isNext ? nextLabel ?? naviI18n("spin.next") : previousLabel ?? naviI18n("spin.previous"),
|
|
83554
84310
|
onPress: e => {
|
|
83555
84311
|
focusMiddle();
|
|
@@ -83596,6 +84352,8 @@ const Spin = ({
|
|
|
83596
84352
|
...valueProps,
|
|
83597
84353
|
min: min,
|
|
83598
84354
|
max: max,
|
|
84355
|
+
minMessage: minMessage,
|
|
84356
|
+
maxMessage: maxMessage,
|
|
83599
84357
|
step: step,
|
|
83600
84358
|
readOnly: readOnly,
|
|
83601
84359
|
disabled: disabled,
|
|
@@ -83629,6 +84387,8 @@ const Spin = ({
|
|
|
83629
84387
|
...valueProps,
|
|
83630
84388
|
min: min,
|
|
83631
84389
|
max: max,
|
|
84390
|
+
minMessage: minMessage,
|
|
84391
|
+
maxMessage: maxMessage,
|
|
83632
84392
|
readOnly: readOnly,
|
|
83633
84393
|
disabled: disabled,
|
|
83634
84394
|
loading: loading,
|
|
@@ -83739,6 +84499,7 @@ const Spin = ({
|
|
|
83739
84499
|
const WayOut = ({
|
|
83740
84500
|
atStart,
|
|
83741
84501
|
commandFor,
|
|
84502
|
+
unavailable,
|
|
83742
84503
|
unavailableMessage,
|
|
83743
84504
|
label,
|
|
83744
84505
|
onPress,
|
|
@@ -83749,10 +84510,10 @@ const WayOut = ({
|
|
|
83749
84510
|
// onPointerUp below).
|
|
83750
84511
|
const pointerDownRef = useRef(null);
|
|
83751
84512
|
const press = e => {
|
|
83752
|
-
if (
|
|
84513
|
+
if (unavailable) {
|
|
83753
84514
|
// Why it does nothing, said where one pressed: a control would have
|
|
83754
84515
|
// done this through its own interaction gate, and this one has none.
|
|
83755
|
-
openCallout(unavailableMessage, {
|
|
84516
|
+
openCallout(unavailableMessage(), {
|
|
83756
84517
|
anchorElement: e.currentTarget,
|
|
83757
84518
|
status: "info",
|
|
83758
84519
|
openingEvent: e
|
|
@@ -83779,8 +84540,8 @@ const WayOut = ({
|
|
|
83779
84540
|
,
|
|
83780
84541
|
role: "button",
|
|
83781
84542
|
"aria-label": label,
|
|
83782
|
-
"aria-disabled":
|
|
83783
|
-
"data-unavailable":
|
|
84543
|
+
"aria-disabled": unavailable ? "true" : undefined,
|
|
84544
|
+
"data-unavailable": unavailable ? "" : undefined
|
|
83784
84545
|
// At the chevron, not beside it: a callout aims its arrow at where the
|
|
83785
84546
|
// anchor's text starts, and there is no text here — only a glyph in the
|
|
83786
84547
|
// middle of the box, which is what one pressed and what the answer is
|
|
@@ -84013,6 +84774,8 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
84013
84774
|
* defaultValue?: number|string,
|
|
84014
84775
|
* min?: number,
|
|
84015
84776
|
* max?: number,
|
|
84777
|
+
* minMessage?: any,
|
|
84778
|
+
* maxMessage?: any,
|
|
84016
84779
|
* step?: number,
|
|
84017
84780
|
* pad?: number,
|
|
84018
84781
|
* loop?: boolean,
|
|
@@ -84118,6 +84881,8 @@ const numberAtStep = (value, count, {
|
|
|
84118
84881
|
* name?: string,
|
|
84119
84882
|
* min?: string,
|
|
84120
84883
|
* max?: string,
|
|
84884
|
+
* minMessage?: any,
|
|
84885
|
+
* maxMessage?: any,
|
|
84121
84886
|
* step?: number,
|
|
84122
84887
|
* lang?: string,
|
|
84123
84888
|
* renderDay?: (day: string) => import("ignore:preact").ComponentChildren,
|
|
@@ -95696,5 +96461,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
95696
96461
|
})
|
|
95697
96462
|
});
|
|
95698
96463
|
|
|
95699
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, ControlSwap, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NetworkPolicyError, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRange, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, dispatchRequestSetUIState, enableDebugActions, enableDebugOnDocumentLoading, enableVirtualKeyboardOverlay, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, getNowHours, getNowHoursRoundedToStep, isCellSelected, isColumnSelected, isNetworkPolicyError, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markAsOutsideTextFlow, markErrorAsDisplayedBy, moveArrayItemByIndex, moveFocusTo, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, preloadUrl, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, routeFallback, scrollActivitySignal, setBaseUrl, setNetworkPolicy, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useNetworkPolicyReason, useOrderedColumns, usePickerState, usePopupMode, useRouteStatus, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useTransitionCover, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
96464
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, ControlSwap, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NetworkPolicyError, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRange, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, activeRouteActionsSignal, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineRouteDefaultTransition, defineRouteTransition, detectHorizontalOverflow, dispatchRequestSetUIState, enableDebugActions, enableDebugOnDocumentLoading, enableVirtualKeyboardOverlay, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, getNowHours, getNowHoursRoundedToStep, isCellSelected, isColumnSelected, isNetworkPolicyError, isRowSelected, isScrolling, isToday, keepPageOnScreen, languagesSignal, localStorageSignal, markAsOutsideTextFlow, markErrorAsDisplayedBy, moveArrayItemByIndex, moveFocusTo, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, preloadUrl, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, routeFallback, scrollActivitySignal, setBaseUrl, setNetworkPolicy, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useNetworkPolicyReason, useOrderedColumns, usePickerState, usePopupMode, useRouteStatus, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useTransitionCover, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
95700
96465
|
//# sourceMappingURL=jsenv_navi.js.map
|