@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/jsenv_navi.js
CHANGED
|
@@ -7,11 +7,11 @@ import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchIntern
|
|
|
7
7
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
8
8
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
9
9
|
import { options, isValidElement, createContext, render as render$1, h, toChildArray, createRef, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
10
|
-
import { useErrorBoundary, useLayoutEffect, useContext, useState, useMemo, useRef, useEffect, useCallback,
|
|
10
|
+
import { useErrorBoundary, useLayoutEffect, useContext, useState, useMemo, useRef, useReducer, useEffect, useCallback, useId } from "preact/hooks";
|
|
11
11
|
import { humanizeI18n, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatTime, formatMinuteDuration, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
|
|
12
12
|
export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
|
|
13
13
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
14
|
-
import { CHAR_CLASS_PRESETS, createValidity,
|
|
14
|
+
import { CHAR_CLASS_PRESETS, createValidity, durationContainsNaN, compareTwoDurations, TYPE_RULE, durationToSeconds, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, parseDuration, durationToISOString } from "@jsenv/validity";
|
|
15
15
|
export { compareTwoDurations, durationContainsNaN, durationToHours, durationToISOString, durationToMinutes, durationToNumber, durationToSeconds, durationToString, parseDuration } from "@jsenv/validity";
|
|
16
16
|
import { Suspense, createPortal, forwardRef } from "preact/compat";
|
|
17
17
|
|
|
@@ -944,8 +944,10 @@ naviI18n.addAll({
|
|
|
944
944
|
},
|
|
945
945
|
});
|
|
946
946
|
|
|
947
|
-
// Spin messages — the ends of what one steps through
|
|
948
|
-
//
|
|
947
|
+
// Spin messages — the ends of what one steps through when the control holding
|
|
948
|
+
// it has no sentence of its own for a bound (a day or a number says its
|
|
949
|
+
// constraint.min/max one, see picker_spin.jsx): said without naming what it is
|
|
950
|
+
// made of, so they fit pages, sizes or any list of items.
|
|
949
951
|
naviI18n.addAll({
|
|
950
952
|
"spin.previous": {
|
|
951
953
|
en: "Previous",
|
|
@@ -3546,8 +3548,11 @@ const actionWeakMap = new WeakMap();
|
|
|
3546
3548
|
* than a first load (data_states.md). Called at the start of every run whose
|
|
3547
3549
|
* value is undefined; the answer replaces it as usual, and `undefined` means
|
|
3548
3550
|
* "nothing known". A resource `GET` uses it to draw the row its store holds.
|
|
3549
|
-
*
|
|
3550
|
-
*
|
|
3551
|
+
* @param {boolean} [rootOptions.inheritData=true] - `false`: a binding moving
|
|
3552
|
+
* to new params starts the new instance without the previous params' answer
|
|
3553
|
+
* (see createActionProxyFromSignal). For an answer that would say something
|
|
3554
|
+
* false about the new params (a `GET` drawing another row), not for one that
|
|
3555
|
+
* stands in while the next one comes (a list of search results).
|
|
3551
3556
|
* @param {{ ms?: number, max?: number }} [rootOptions.keep] - how long an
|
|
3552
3557
|
* answer stays good once it has landed. Without it an answer lives exactly as
|
|
3553
3558
|
* long as something references it: the screen that asked goes away, and
|
|
@@ -3590,6 +3595,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3590
3595
|
resultToValue,
|
|
3591
3596
|
valueToData,
|
|
3592
3597
|
provisionalValue,
|
|
3598
|
+
inheritData = true,
|
|
3593
3599
|
dataDefault,
|
|
3594
3600
|
data = dataDefault,
|
|
3595
3601
|
|
|
@@ -4253,6 +4259,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4253
4259
|
const privateProperties = {
|
|
4254
4260
|
valueInitial,
|
|
4255
4261
|
provisionalValue,
|
|
4262
|
+
inheritData,
|
|
4256
4263
|
|
|
4257
4264
|
performRun,
|
|
4258
4265
|
performReset,
|
|
@@ -4285,7 +4292,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4285
4292
|
* a list of filters changes, providing real-time results without user interaction.
|
|
4286
4293
|
* @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
|
|
4287
4294
|
* By default (true), the proxy carries over the previous target's value and error into the new action —
|
|
4288
|
-
* unless the action declares
|
|
4295
|
+
* unless the action itself declares `inheritData: false` (createAction), which a resource `GET` does.
|
|
4289
4296
|
* This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
|
|
4290
4297
|
* new action completes, not when it starts loading. Code that needs to distinguish loading state can still
|
|
4291
4298
|
* check `action.runningState`, while code that just reads `action.data` always sees the most recent
|
|
@@ -4332,12 +4339,12 @@ const createActionProxyFromSignal = (
|
|
|
4332
4339
|
let currentActionPrivateProperties = getActionPrivateProperties(action);
|
|
4333
4340
|
let actionTargetPreviousWeakRef = null;
|
|
4334
4341
|
|
|
4335
|
-
// An action
|
|
4336
|
-
//
|
|
4337
|
-
//
|
|
4338
|
-
//
|
|
4342
|
+
// An action whose answer for other params would be false for these (a
|
|
4343
|
+
// resource GET: another row) says so itself, and is not handed it. What it
|
|
4344
|
+
// does not know stays unknown: a skeleton, or its provisionalValue, never a
|
|
4345
|
+
// different row.
|
|
4339
4346
|
const inheritsAnswer =
|
|
4340
|
-
inheritData &&
|
|
4347
|
+
inheritData && getActionPrivateProperties(action).inheritData;
|
|
4341
4348
|
const createTarget = (params) => {
|
|
4342
4349
|
if (inheritsAnswer) {
|
|
4343
4350
|
const previousActionTarget = actionTargetPreviousWeakRef?.deref();
|
|
@@ -10240,6 +10247,9 @@ const paramsTargetCanPlace = (redirectRoute, urlParams) => {
|
|
|
10240
10247
|
|
|
10241
10248
|
let setupRoutesCalled = false;
|
|
10242
10249
|
let activeRouteSet = null;
|
|
10250
|
+
// True once the routes have been matched against an address. Before that, no
|
|
10251
|
+
// route matching says nothing about the page: nothing has been asked yet.
|
|
10252
|
+
const routedSignal = signal(false);
|
|
10243
10253
|
|
|
10244
10254
|
const routePreloadMap = new WeakMap();
|
|
10245
10255
|
const registerRoutePreload = (route, preload) => {
|
|
@@ -10664,6 +10674,7 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
10664
10674
|
}
|
|
10665
10675
|
// Reset flag after URL -> Signal synchronization is complete
|
|
10666
10676
|
isUpdatingRoutesFromUrl = false;
|
|
10677
|
+
routedSignal.value = true;
|
|
10667
10678
|
Object.assign(returnValue, { matchingRouteSet });
|
|
10668
10679
|
for (const [route, repairedParams] of repairedParamsByRoute) {
|
|
10669
10680
|
route.replaceParams(repairedParams, {
|
|
@@ -10713,6 +10724,7 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
10713
10724
|
droppingRouteSet = null;
|
|
10714
10725
|
setupRoutesCalled = false;
|
|
10715
10726
|
activeRouteSet = null;
|
|
10727
|
+
routedSignal.value = false;
|
|
10716
10728
|
};
|
|
10717
10729
|
return {
|
|
10718
10730
|
updateRoutes,
|
|
@@ -10779,6 +10791,42 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
10779
10791
|
return `${urlObject.origin}${urlObject.pathname}`;
|
|
10780
10792
|
};
|
|
10781
10793
|
|
|
10794
|
+
/**
|
|
10795
|
+
* What a component can learn about the page it is rendered in, from the route
|
|
10796
|
+
* container rendering that page (see useContentKeepingLeavingPages in
|
|
10797
|
+
* route.jsx). `null` outside of any route container.
|
|
10798
|
+
*
|
|
10799
|
+
* - `isShown()` — whether the container would still show the page. False as
|
|
10800
|
+
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
10801
|
+
* hidden until the route transition leaving it is over, and nothing it reads
|
|
10802
|
+
* is worth a render any more. False for good once it is kept while leaving: a
|
|
10803
|
+
* return to its route mounts the page anew. Asked outside any render — by a
|
|
10804
|
+
* subscription deciding whether to re-render — and it reads the routes
|
|
10805
|
+
* untracked, so asking subscribes nobody to them.
|
|
10806
|
+
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
10807
|
+
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
10808
|
+
* bar) steps out rather than showing with the page arriving.
|
|
10809
|
+
*/
|
|
10810
|
+
const RoutePageContext = createContext(null);
|
|
10811
|
+
|
|
10812
|
+
/**
|
|
10813
|
+
* A container has put its page on screen — or as much of it as it can.
|
|
10814
|
+
*
|
|
10815
|
+
* A route matching is a signal changing, and the page it selects reaches the
|
|
10816
|
+
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
10817
|
+
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
10818
|
+
* as it has been decided (a travel about to have its picture taken by the
|
|
10819
|
+
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
10820
|
+
*
|
|
10821
|
+
* A page waiting on data is announced too, by the boundary showing its loading
|
|
10822
|
+
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
10823
|
+
* screen is what the browser is about to take a picture of, and a page that
|
|
10824
|
+
* cannot render yet would otherwise be waited on until the transition dies of
|
|
10825
|
+
* it. It lives in a module of its own for that: the async layer says it as much
|
|
10826
|
+
* as the router does, and neither can import the other.
|
|
10827
|
+
*/
|
|
10828
|
+
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
10829
|
+
|
|
10782
10830
|
const documentStateSignal = signal(null);
|
|
10783
10831
|
const useDocumentState = () => {
|
|
10784
10832
|
return documentStateSignal.value;
|
|
@@ -11038,24 +11086,6 @@ const readTopInWindow = element => {
|
|
|
11038
11086
|
return element.getBoundingClientRect().top;
|
|
11039
11087
|
};
|
|
11040
11088
|
|
|
11041
|
-
/**
|
|
11042
|
-
* A container has put its page on screen — or as much of it as it can.
|
|
11043
|
-
*
|
|
11044
|
-
* A route matching is a signal changing, and the page it selects reaches the
|
|
11045
|
-
* DOM only once Preact has rendered — an unknown number of passes later, in an
|
|
11046
|
-
* unknown number of microtasks. Anyone who needs the page as it IS rather than
|
|
11047
|
-
* as it has been decided (a travel about to have its picture taken by the
|
|
11048
|
-
* browser, see route_travel.jsx) waits for this instead of counting.
|
|
11049
|
-
*
|
|
11050
|
-
* A page waiting on data is announced too, by the boundary showing its loading
|
|
11051
|
-
* state (see Loading in use_async_data.jsx): what the container could put on
|
|
11052
|
-
* screen is what the browser is about to take a picture of, and a page that
|
|
11053
|
-
* cannot render yet would otherwise be waited on until the transition dies of
|
|
11054
|
-
* it. It lives in a module of its own for that: the async layer says it as much
|
|
11055
|
-
* as the router does, and neither can import the other.
|
|
11056
|
-
*/
|
|
11057
|
-
const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
11058
|
-
|
|
11059
11089
|
/**
|
|
11060
11090
|
* Where a page was left, given back when one comes back to it.
|
|
11061
11091
|
*
|
|
@@ -12450,6 +12480,7 @@ browserIntegration.handleActionTask;
|
|
|
12450
12480
|
|
|
12451
12481
|
const NO_OP = () => {};
|
|
12452
12482
|
const NO_ID_GIVEN = [undefined, NO_OP, NO_OP];
|
|
12483
|
+
const countRenders$1 = (count) => count + 1;
|
|
12453
12484
|
// What the computed below answers for a key the document state does not hold:
|
|
12454
12485
|
// key presence is the flag, and the value under a present key may be anything,
|
|
12455
12486
|
// undefined included.
|
|
@@ -12478,7 +12509,36 @@ const useNavStateBasic = (
|
|
|
12478
12509
|
[id],
|
|
12479
12510
|
);
|
|
12480
12511
|
const keyValue = keyValueComputed.value;
|
|
12481
|
-
|
|
12512
|
+
// A page the address has left keeps reading the entry it was shown on until
|
|
12513
|
+
// it is taken down (kept hidden while a route transition plays): the new
|
|
12514
|
+
// entry would close what was open there — a Picker as a cancel — where the
|
|
12515
|
+
// same page taken down at once is only unmounted. Its render still happens:
|
|
12516
|
+
// the state is written before the routes say the page is left.
|
|
12517
|
+
const routePage = useContext(RoutePageContext);
|
|
12518
|
+
const keyValueReadRef = useRef(keyValue);
|
|
12519
|
+
const keyValueWithheld =
|
|
12520
|
+
keyValue !== keyValueReadRef.current &&
|
|
12521
|
+
routePage !== null &&
|
|
12522
|
+
!routePage.isShown();
|
|
12523
|
+
if (!keyValueWithheld) {
|
|
12524
|
+
keyValueReadRef.current = keyValue;
|
|
12525
|
+
}
|
|
12526
|
+
const keyValueRead = keyValueReadRef.current;
|
|
12527
|
+
// Owed rather than dropped, as in useAsyncData: the address can come back
|
|
12528
|
+
// before the container renders (a redirect straight back), and the page then
|
|
12529
|
+
// stays on screen.
|
|
12530
|
+
const [, rerender] = useReducer(countRenders$1, 0);
|
|
12531
|
+
useEffect(() => {
|
|
12532
|
+
if (!keyValueWithheld) {
|
|
12533
|
+
return undefined;
|
|
12534
|
+
}
|
|
12535
|
+
return observeRouteRender(() => {
|
|
12536
|
+
if (routePage.isShown()) {
|
|
12537
|
+
rerender();
|
|
12538
|
+
}
|
|
12539
|
+
});
|
|
12540
|
+
}, [keyValueWithheld]);
|
|
12541
|
+
const keyInState = keyValueRead !== KEY_NOT_IN_STATE;
|
|
12482
12542
|
const onLeaveRef = useRef(onLeave);
|
|
12483
12543
|
onLeaveRef.current = onLeave;
|
|
12484
12544
|
const prevKeyInStateRef = useRef(keyInState);
|
|
@@ -12508,7 +12568,7 @@ const useNavStateBasic = (
|
|
|
12508
12568
|
effectiveType = "replace";
|
|
12509
12569
|
}
|
|
12510
12570
|
|
|
12511
|
-
const currentValue = keyInState ?
|
|
12571
|
+
const currentValue = keyInState ? keyValueRead : defaultValue;
|
|
12512
12572
|
|
|
12513
12573
|
if (debug) {
|
|
12514
12574
|
console.debug(`useNavState(${id}) current value is ${currentValue}`);
|
|
@@ -12576,6 +12636,8 @@ const useNavStateBasic = (
|
|
|
12576
12636
|
/**
|
|
12577
12637
|
* Stores a named value in the browser's document state and returns it reactively.
|
|
12578
12638
|
* The component re-renders whenever the value changes (navigation, back/forward button).
|
|
12639
|
+
* In a page the address has left — kept hidden while a route transition plays —
|
|
12640
|
+
* the value stays the one of the entry that page was shown on.
|
|
12579
12641
|
*
|
|
12580
12642
|
* @param {string} id
|
|
12581
12643
|
* Unique key used to store the value in document state. Must be stable across renders.
|
|
@@ -14981,6 +15043,13 @@ const createOpenController = (
|
|
|
14981
15043
|
change();
|
|
14982
15044
|
};
|
|
14983
15045
|
const runChange = (change, { opened, event }) => {
|
|
15046
|
+
// What the controller answers about itself is the decision, written before
|
|
15047
|
+
// the change runs, whatever runs it. Whoever asks meanwhile reads it: the
|
|
15048
|
+
// render an opening forces (onOpen, mountContent) reads the open state
|
|
15049
|
+
// the caller holds, and an onOpen writing the popup's own `signal` has
|
|
15050
|
+
// useOpenPropsEffectOnOpenController asking for this very opening — a
|
|
15051
|
+
// second one, nested in the first, if `opened` still said closed.
|
|
15052
|
+
controller.opened = opened;
|
|
14984
15053
|
const applyChange = () => {
|
|
14985
15054
|
// Recorded with the change itself: what the DOM shows is what a render
|
|
14986
15055
|
// landing between the ask and the picture must draw (see `openedInDom`).
|
|
@@ -14996,12 +15065,8 @@ const createOpenController = (
|
|
|
14996
15065
|
applyChange();
|
|
14997
15066
|
return;
|
|
14998
15067
|
}
|
|
14999
|
-
//
|
|
15000
|
-
//
|
|
15001
|
-
// useOpenPropsEffectOnOpenController, which writes it back into the
|
|
15002
|
-
// caller's own signal), and a request arriving before the change lands
|
|
15003
|
-
// runs it first rather than reading a DOM that disagrees.
|
|
15004
|
-
controller.opened = opened;
|
|
15068
|
+
// A request arriving before the change lands runs it first rather than
|
|
15069
|
+
// reading a DOM that disagrees.
|
|
15005
15070
|
changeAwaitingTransition = applyChange;
|
|
15006
15071
|
transitionChange(
|
|
15007
15072
|
() => {
|
|
@@ -15014,7 +15079,6 @@ const createOpenController = (
|
|
|
15014
15079
|
};
|
|
15015
15080
|
|
|
15016
15081
|
const performClose = (closeEvent) => {
|
|
15017
|
-
controller.opened = false;
|
|
15018
15082
|
// Read before any close effect touches the DOM: closing a native <dialog>
|
|
15019
15083
|
// hands the focus back to whatever held it at showModal() time, so by the
|
|
15020
15084
|
// time the close cleanup runs, the popup's content has already lost the
|
|
@@ -15282,7 +15346,6 @@ const createOpenController = (
|
|
|
15282
15346
|
// told it opened right after (see popup_content_mount.js and
|
|
15283
15347
|
// use_displayed_layout_effect.js).
|
|
15284
15348
|
controller.openedInDom = true;
|
|
15285
|
-
controller.opened = true;
|
|
15286
15349
|
// Which press it opened during, so the release of that press is not
|
|
15287
15350
|
// read as somebody dismissing it (see openedDuringThisPress).
|
|
15288
15351
|
controller.pressCountAtOpen = pressCount;
|
|
@@ -15709,8 +15772,10 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
15709
15772
|
return undefined;
|
|
15710
15773
|
}
|
|
15711
15774
|
// Skip when the controller is already in the desired state.
|
|
15712
|
-
// openController.opened
|
|
15713
|
-
//
|
|
15775
|
+
// openController.opened is what the controller decided, written before an
|
|
15776
|
+
// opening renders anything (see runChange), so it is the authoritative
|
|
15777
|
+
// check against feedback loops — the popup's own onOpen writing its
|
|
15778
|
+
// `signal` included.
|
|
15714
15779
|
if (open === openController.opened) {
|
|
15715
15780
|
return undefined;
|
|
15716
15781
|
}
|
|
@@ -19553,18 +19618,17 @@ const MIN_CONSTRAINT = {
|
|
|
19553
19618
|
if (cmp === null || cmp >= 0) {
|
|
19554
19619
|
return null;
|
|
19555
19620
|
}
|
|
19556
|
-
|
|
19557
|
-
return naviI18n("constraint.min.duration.default", {
|
|
19558
|
-
min: formatDuration(min),
|
|
19559
|
-
});
|
|
19621
|
+
return boundMessage(field, "min");
|
|
19560
19622
|
}
|
|
19561
19623
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
19562
19624
|
return null;
|
|
19563
19625
|
}
|
|
19564
|
-
const
|
|
19565
|
-
|
|
19626
|
+
const min = field.controlHostProps.min;
|
|
19627
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
19628
|
+
if (min === undefined || min === null || min === "") {
|
|
19566
19629
|
return null;
|
|
19567
19630
|
}
|
|
19631
|
+
const minString = String(min);
|
|
19568
19632
|
const type = field.controlHostProps.type;
|
|
19569
19633
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
19570
19634
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -19580,40 +19644,16 @@ const MIN_CONSTRAINT = {
|
|
|
19580
19644
|
if (isNaN(numericValue)) {
|
|
19581
19645
|
return null;
|
|
19582
19646
|
}
|
|
19583
|
-
if (numericValue
|
|
19584
|
-
|
|
19585
|
-
return naviI18n(`constraint.min.hour.default`, {
|
|
19586
|
-
min: minString,
|
|
19587
|
-
});
|
|
19588
|
-
}
|
|
19589
|
-
if (naviInputType === "minute") {
|
|
19590
|
-
return naviI18n(`constraint.min.minute.default`, {
|
|
19591
|
-
min: minString,
|
|
19592
|
-
});
|
|
19593
|
-
}
|
|
19594
|
-
if (naviInputType === "second") {
|
|
19595
|
-
return naviI18n(`constraint.min.second.default`, {
|
|
19596
|
-
min: minString,
|
|
19597
|
-
});
|
|
19598
|
-
}
|
|
19599
|
-
if (naviInputType === "percentage") {
|
|
19600
|
-
return naviI18n(`constraint.min.percentage.default`, {
|
|
19601
|
-
min: minString,
|
|
19602
|
-
});
|
|
19603
|
-
}
|
|
19604
|
-
return naviI18n(`constraint.min.number.default`, {
|
|
19605
|
-
min: minString,
|
|
19606
|
-
});
|
|
19647
|
+
if (numericValue >= minNumber) {
|
|
19648
|
+
return null;
|
|
19607
19649
|
}
|
|
19608
|
-
return
|
|
19650
|
+
return boundMessage(field, "min");
|
|
19609
19651
|
}
|
|
19610
19652
|
if (type === "time") {
|
|
19611
19653
|
const [minHours, minMinutes] = minString.split(":").map(Number);
|
|
19612
19654
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
19613
19655
|
if (hours < minHours || (hours === minHours && minutes < minMinutes)) {
|
|
19614
|
-
return
|
|
19615
|
-
min: minString,
|
|
19616
|
-
});
|
|
19656
|
+
return boundMessage(field, "min");
|
|
19617
19657
|
}
|
|
19618
19658
|
return null;
|
|
19619
19659
|
}
|
|
@@ -19621,18 +19661,13 @@ const MIN_CONSTRAINT = {
|
|
|
19621
19661
|
// so they never need a min/max validation message.
|
|
19622
19662
|
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
19623
19663
|
if (valueAsString < minString) {
|
|
19624
|
-
|
|
19625
|
-
if (minString === todayIso) {
|
|
19626
|
-
return naviI18n("constraint.min.date.today.default");
|
|
19627
|
-
}
|
|
19628
|
-
return naviI18n("constraint.min.date.default", {
|
|
19629
|
-
min: formatDateIso(minString, type),
|
|
19630
|
-
});
|
|
19664
|
+
return boundMessage(field, "min");
|
|
19631
19665
|
}
|
|
19632
19666
|
return null;
|
|
19633
19667
|
}
|
|
19634
19668
|
return null;
|
|
19635
19669
|
},
|
|
19670
|
+
messageAtBound: (field) => boundMessage(field, "min"),
|
|
19636
19671
|
};
|
|
19637
19672
|
CONSTRAINT_ATTRIBUTE_SET.add("min");
|
|
19638
19673
|
|
|
@@ -19652,18 +19687,17 @@ const MAX_CONSTRAINT = {
|
|
|
19652
19687
|
if (cmp === null || cmp <= 0) {
|
|
19653
19688
|
return null;
|
|
19654
19689
|
}
|
|
19655
|
-
|
|
19656
|
-
return naviI18n("constraint.max.duration.default", {
|
|
19657
|
-
max: formatDuration(max),
|
|
19658
|
-
});
|
|
19690
|
+
return boundMessage(field, "max");
|
|
19659
19691
|
}
|
|
19660
19692
|
if (field.controlType !== "input" && field.controlType !== "picker") {
|
|
19661
19693
|
return null;
|
|
19662
19694
|
}
|
|
19663
|
-
const
|
|
19664
|
-
|
|
19695
|
+
const max = field.controlHostProps.max;
|
|
19696
|
+
// 0 is a bound like any other: only a bound nobody gave is skipped.
|
|
19697
|
+
if (max === undefined || max === null || max === "") {
|
|
19665
19698
|
return null;
|
|
19666
19699
|
}
|
|
19700
|
+
const maxString = String(max);
|
|
19667
19701
|
const type = field.controlHostProps.type;
|
|
19668
19702
|
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
19669
19703
|
const valueAsString = uiStateAsText(field.uiState);
|
|
@@ -19682,38 +19716,13 @@ const MAX_CONSTRAINT = {
|
|
|
19682
19716
|
if (numericValue <= maxNumber) {
|
|
19683
19717
|
return null;
|
|
19684
19718
|
}
|
|
19685
|
-
|
|
19686
|
-
if (naviInputType === "hour") {
|
|
19687
|
-
return naviI18n(`constraint.max.hour.default`, {
|
|
19688
|
-
max: maxString,
|
|
19689
|
-
});
|
|
19690
|
-
}
|
|
19691
|
-
if (naviInputType === "minute") {
|
|
19692
|
-
return naviI18n(`constraint.max.minute.default`, {
|
|
19693
|
-
max: maxString,
|
|
19694
|
-
});
|
|
19695
|
-
}
|
|
19696
|
-
if (naviInputType === "second") {
|
|
19697
|
-
return naviI18n(`constraint.max.second.default`, {
|
|
19698
|
-
max: maxString,
|
|
19699
|
-
});
|
|
19700
|
-
}
|
|
19701
|
-
if (naviInputType === "percentage") {
|
|
19702
|
-
return naviI18n(`constraint.max.percentage.default`, {
|
|
19703
|
-
max: maxString,
|
|
19704
|
-
});
|
|
19705
|
-
}
|
|
19706
|
-
return naviI18n(`constraint.max.number.default`, {
|
|
19707
|
-
max: maxString,
|
|
19708
|
-
});
|
|
19719
|
+
return boundMessage(field, "max");
|
|
19709
19720
|
}
|
|
19710
19721
|
if (type === "time") {
|
|
19711
19722
|
const [maxHours, maxMinutes] = maxString.split(":").map(Number);
|
|
19712
19723
|
const [hours, minutes] = valueAsString.split(":").map(Number);
|
|
19713
19724
|
if (hours > maxHours || (hours === maxHours && minutes > maxMinutes)) {
|
|
19714
|
-
return
|
|
19715
|
-
max: maxString,
|
|
19716
|
-
});
|
|
19725
|
+
return boundMessage(field, "max");
|
|
19717
19726
|
}
|
|
19718
19727
|
return null;
|
|
19719
19728
|
}
|
|
@@ -19721,17 +19730,11 @@ const MAX_CONSTRAINT = {
|
|
|
19721
19730
|
if (valueAsString <= maxString) {
|
|
19722
19731
|
return null;
|
|
19723
19732
|
}
|
|
19724
|
-
|
|
19725
|
-
const todayIso = getTodayIso(type);
|
|
19726
|
-
if (maxString === todayIso) {
|
|
19727
|
-
return naviI18n("constraint.max.date.today.default");
|
|
19728
|
-
}
|
|
19729
|
-
return naviI18n("constraint.max.date.default", {
|
|
19730
|
-
max: formatDateIso(maxString, type),
|
|
19731
|
-
});
|
|
19733
|
+
return boundMessage(field, "max");
|
|
19732
19734
|
}
|
|
19733
19735
|
return null;
|
|
19734
19736
|
},
|
|
19737
|
+
messageAtBound: (field) => boundMessage(field, "max"),
|
|
19735
19738
|
};
|
|
19736
19739
|
CONSTRAINT_ATTRIBUTE_SET.add("max");
|
|
19737
19740
|
|
|
@@ -19926,6 +19929,51 @@ const formatMsToTime = (ms, showSeconds) => {
|
|
|
19926
19929
|
return `${hh}:${mm}:${ss}`;
|
|
19927
19930
|
};
|
|
19928
19931
|
|
|
19932
|
+
// What a bound says, whatever the value: the sentence `check` gives a value
|
|
19933
|
+
// past it, and the one a spin gives when its chevron is refused there
|
|
19934
|
+
// (picker_spin.jsx) — one sentence, so the two refuse in the same words. null
|
|
19935
|
+
// for a type that has nothing to say about a bound.
|
|
19936
|
+
const boundMessage = (field, bound) => {
|
|
19937
|
+
const boundValue = field.controlHostProps[bound];
|
|
19938
|
+
if (field.controlType === "duration_group") {
|
|
19939
|
+
return naviI18n(`constraint.${bound}.duration.default`, {
|
|
19940
|
+
[bound]: formatDuration(boundValue),
|
|
19941
|
+
});
|
|
19942
|
+
}
|
|
19943
|
+
const type = field.controlHostProps.type;
|
|
19944
|
+
const naviInputType = field.controlHostProps["navi-input-type"];
|
|
19945
|
+
if (isNumberInput(type, naviInputType)) {
|
|
19946
|
+
const numberKind = NUMBER_BOUND_KIND_SET.has(naviInputType)
|
|
19947
|
+
? naviInputType
|
|
19948
|
+
: "number";
|
|
19949
|
+
return naviI18n(`constraint.${bound}.${numberKind}.default`, {
|
|
19950
|
+
[bound]: String(boundValue),
|
|
19951
|
+
});
|
|
19952
|
+
}
|
|
19953
|
+
if (type === "time") {
|
|
19954
|
+
return naviI18n(`constraint.${bound}.time.default`, {
|
|
19955
|
+
[bound]: boundValue,
|
|
19956
|
+
});
|
|
19957
|
+
}
|
|
19958
|
+
if (DATE_INPUT_TYPE_SET.has(type)) {
|
|
19959
|
+
if (boundValue === getTodayIso(type)) {
|
|
19960
|
+
return naviI18n(`constraint.${bound}.date.today.default`);
|
|
19961
|
+
}
|
|
19962
|
+
return naviI18n(`constraint.${bound}.date.default`, {
|
|
19963
|
+
[bound]: formatDateIso(boundValue, type),
|
|
19964
|
+
});
|
|
19965
|
+
}
|
|
19966
|
+
return null;
|
|
19967
|
+
};
|
|
19968
|
+
// The numbers whose bound is said with what they count ("Max 23 heures");
|
|
19969
|
+
// any other is "Ce nombre".
|
|
19970
|
+
const NUMBER_BOUND_KIND_SET = new Set([
|
|
19971
|
+
"hour",
|
|
19972
|
+
"minute",
|
|
19973
|
+
"second",
|
|
19974
|
+
"percentage",
|
|
19975
|
+
]);
|
|
19976
|
+
|
|
19929
19977
|
const getTodayIso = (inputType) => {
|
|
19930
19978
|
const now = new Date();
|
|
19931
19979
|
const yyyy = now.getFullYear();
|
|
@@ -25793,6 +25841,171 @@ const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
|
|
|
25793
25841
|
return childStore !== undefined && childStore === getChildStore(ownerId);
|
|
25794
25842
|
};
|
|
25795
25843
|
|
|
25844
|
+
/*
|
|
25845
|
+
* What a previous document kept of the page it was on (see keepPageOnScreen in
|
|
25846
|
+
* nav/page_kept.js), waiting for the reads that draw it again, and what those
|
|
25847
|
+
* reads are made of on the way out: rows with their relations inline.
|
|
25848
|
+
*
|
|
25849
|
+
* A kept read is found by the read's name and the params it was asked with
|
|
25850
|
+
* (getKeptReadKey). It is handed out once: the first run of the read draws it
|
|
25851
|
+
* while its request is out, and from then on the store holds the rows. Handed
|
|
25852
|
+
* out again, after a reset say, it would write the previous document's rows
|
|
25853
|
+
* over a fresher answer.
|
|
25854
|
+
*
|
|
25855
|
+
* This module is the half the resources see: `resource()` and the range reader
|
|
25856
|
+
* take from it, and say which of their reads can be kept at all. Which reads
|
|
25857
|
+
* are on the page on screen, and what the slot is written into, is
|
|
25858
|
+
* page_kept.js.
|
|
25859
|
+
*/
|
|
25860
|
+
|
|
25861
|
+
// key → what the read answered in the previous document, not handed out yet
|
|
25862
|
+
const offeredReadMap = new Map();
|
|
25863
|
+
const offerKeptReads = (entryMap) => {
|
|
25864
|
+
offeredReadMap.clear();
|
|
25865
|
+
for (const [key, entry] of entryMap) {
|
|
25866
|
+
offeredReadMap.set(key, entry);
|
|
25867
|
+
}
|
|
25868
|
+
};
|
|
25869
|
+
const withdrawKeptReads = () => {
|
|
25870
|
+
offeredReadMap.clear();
|
|
25871
|
+
};
|
|
25872
|
+
const takeKeptRead = (key) => {
|
|
25873
|
+
const entry = offeredReadMap.get(key);
|
|
25874
|
+
if (entry === undefined) {
|
|
25875
|
+
return undefined;
|
|
25876
|
+
}
|
|
25877
|
+
offeredReadMap.delete(key);
|
|
25878
|
+
return entry;
|
|
25879
|
+
};
|
|
25880
|
+
|
|
25881
|
+
// The root actions whose answer is rows of a store, written and read back the
|
|
25882
|
+
// same way: a resource's GET and GET_MANY. Anything else a route runs (a page's
|
|
25883
|
+
// code, a computation) has nothing a later document could draw.
|
|
25884
|
+
const keepableReadWeakSet = new WeakSet();
|
|
25885
|
+
const markKeepableRead = (rootAction) => {
|
|
25886
|
+
keepableReadWeakSet.add(rootAction);
|
|
25887
|
+
};
|
|
25888
|
+
const isKeepableRead = (action) => {
|
|
25889
|
+
const rootAction = action.rootAction || action;
|
|
25890
|
+
return keepableReadWeakSet.has(rootAction);
|
|
25891
|
+
};
|
|
25892
|
+
|
|
25893
|
+
// The range readers a <List.Items> on screen reads through, each with the number
|
|
25894
|
+
// of lists reading it: the reads of the page a route action does not cover.
|
|
25895
|
+
const shownRangeReadersSignal = signal(new Map());
|
|
25896
|
+
const showRangeReader = (reader) => {
|
|
25897
|
+
const shownMap = new Map(shownRangeReadersSignal.peek());
|
|
25898
|
+
shownMap.set(reader, (shownMap.get(reader) || 0) + 1);
|
|
25899
|
+
shownRangeReadersSignal.value = shownMap;
|
|
25900
|
+
return () => {
|
|
25901
|
+
const shownMapNow = new Map(shownRangeReadersSignal.peek());
|
|
25902
|
+
const count = shownMapNow.get(reader);
|
|
25903
|
+
if (count === 1) {
|
|
25904
|
+
shownMapNow.delete(reader);
|
|
25905
|
+
} else {
|
|
25906
|
+
shownMapNow.set(reader, count - 1);
|
|
25907
|
+
}
|
|
25908
|
+
shownRangeReadersSignal.value = shownMapNow;
|
|
25909
|
+
};
|
|
25910
|
+
};
|
|
25911
|
+
|
|
25912
|
+
// A row already in the store is the fresher one: the kept copy only stands in
|
|
25913
|
+
// for rows nothing has answered yet. Its relations are written as they were
|
|
25914
|
+
// kept, which a real answer corrects like any other.
|
|
25915
|
+
const upsertKeptItem = (store, keptItem) => {
|
|
25916
|
+
const id = keptItem[store.idKey];
|
|
25917
|
+
if (id !== undefined) {
|
|
25918
|
+
const existing = store.select(id);
|
|
25919
|
+
if (existing) {
|
|
25920
|
+
return existing;
|
|
25921
|
+
}
|
|
25922
|
+
}
|
|
25923
|
+
return store.upsert(keptItem);
|
|
25924
|
+
};
|
|
25925
|
+
|
|
25926
|
+
const getKeptReadKey = (name, params) => {
|
|
25927
|
+
return `${name}|${getParamsKey(params)}`;
|
|
25928
|
+
};
|
|
25929
|
+
// `true` is what a route action without params is bound to (see routeAction),
|
|
25930
|
+
// and a key holding undefined says nothing a missing one does not.
|
|
25931
|
+
const getParamsKey = (params) => {
|
|
25932
|
+
if (params === undefined || params === NO_PARAMS || params === true) {
|
|
25933
|
+
return "";
|
|
25934
|
+
}
|
|
25935
|
+
if (isPlainParams(params)) {
|
|
25936
|
+
const keys = Object.keys(params).filter((key) => params[key] !== undefined);
|
|
25937
|
+
if (keys.length === 0) {
|
|
25938
|
+
return "";
|
|
25939
|
+
}
|
|
25940
|
+
}
|
|
25941
|
+
return stableStringify(params);
|
|
25942
|
+
};
|
|
25943
|
+
const isPlainParams = (value) => {
|
|
25944
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25945
|
+
};
|
|
25946
|
+
const stableStringify = (value) => {
|
|
25947
|
+
if (value === null || typeof value !== "object") {
|
|
25948
|
+
return JSON.stringify(value);
|
|
25949
|
+
}
|
|
25950
|
+
if (Array.isArray(value)) {
|
|
25951
|
+
return `[${value.map(stableStringify).join(",")}]`;
|
|
25952
|
+
}
|
|
25953
|
+
const keys = Object.keys(value)
|
|
25954
|
+
.filter((key) => value[key] !== undefined)
|
|
25955
|
+
.sort();
|
|
25956
|
+
const parts = [];
|
|
25957
|
+
for (const key of keys) {
|
|
25958
|
+
parts.push(`${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
25959
|
+
}
|
|
25960
|
+
return `{${parts.join(",")}}`;
|
|
25961
|
+
};
|
|
25962
|
+
|
|
25963
|
+
// What a read's data is made of, ready to be written: a row, or the rows of a
|
|
25964
|
+
// list, each with its relations inline.
|
|
25965
|
+
const serializeData = (data) => {
|
|
25966
|
+
if (Array.isArray(data)) {
|
|
25967
|
+
return data.map((item) => serializeItem(item, new Set()));
|
|
25968
|
+
}
|
|
25969
|
+
return serializeItem(data, new Set());
|
|
25970
|
+
};
|
|
25971
|
+
// The item with its relations inline: a relation value stands for what its
|
|
25972
|
+
// signal holds (the child row, the child rows, or nothing), a row reached
|
|
25973
|
+
// twice on the same path is written as its id — the setters accept both.
|
|
25974
|
+
const serializeItem = (item, ancestorSet) => {
|
|
25975
|
+
ancestorSet.add(item);
|
|
25976
|
+
const serialized = {};
|
|
25977
|
+
for (const key of Object.keys(item)) {
|
|
25978
|
+
serialized[key] = serializeValue(item[key], ancestorSet);
|
|
25979
|
+
}
|
|
25980
|
+
ancestorSet.delete(item);
|
|
25981
|
+
return serialized;
|
|
25982
|
+
};
|
|
25983
|
+
const serializeValue = (value, ancestorSet) => {
|
|
25984
|
+
if (value === null || typeof value !== "object") {
|
|
25985
|
+
return value;
|
|
25986
|
+
}
|
|
25987
|
+
if (Array.isArray(value)) {
|
|
25988
|
+
return value.map((entry) => serializeValue(entry, ancestorSet));
|
|
25989
|
+
}
|
|
25990
|
+
const objectSignal = value[SYMBOL_OBJECT_SIGNAL];
|
|
25991
|
+
if (objectSignal) {
|
|
25992
|
+
const held = objectSignal.value;
|
|
25993
|
+
if (held === undefined || held === null) {
|
|
25994
|
+
return null;
|
|
25995
|
+
}
|
|
25996
|
+
return serializeValue(held, ancestorSet);
|
|
25997
|
+
}
|
|
25998
|
+
if (typeof value.toJSON === "function") {
|
|
25999
|
+
return value;
|
|
26000
|
+
}
|
|
26001
|
+
if (ancestorSet.has(value)) {
|
|
26002
|
+
return Object.hasOwn(value, SYMBOL_IDENTITY)
|
|
26003
|
+
? value[SYMBOL_IDENTITY]
|
|
26004
|
+
: null;
|
|
26005
|
+
}
|
|
26006
|
+
return serializeItem(value, ancestorSet);
|
|
26007
|
+
};
|
|
26008
|
+
|
|
25796
26009
|
const paramScopeWeakSet = createIterableWeakSet();
|
|
25797
26010
|
let paramScopeIdCounter = 0;
|
|
25798
26011
|
const getParamScope = (params) => {
|
|
@@ -25963,66 +26176,6 @@ const createResourcePersistence = (
|
|
|
25963
26176
|
};
|
|
25964
26177
|
};
|
|
25965
26178
|
|
|
25966
|
-
const getParamsKey = (params) => {
|
|
25967
|
-
if (params === undefined || params === NO_PARAMS) {
|
|
25968
|
-
return "";
|
|
25969
|
-
}
|
|
25970
|
-
return stableStringify(params);
|
|
25971
|
-
};
|
|
25972
|
-
|
|
25973
|
-
const stableStringify = (value) => {
|
|
25974
|
-
if (value === null || typeof value !== "object") {
|
|
25975
|
-
return JSON.stringify(value);
|
|
25976
|
-
}
|
|
25977
|
-
if (Array.isArray(value)) {
|
|
25978
|
-
return `[${value.map(stableStringify).join(",")}]`;
|
|
25979
|
-
}
|
|
25980
|
-
const keys = Object.keys(value).sort();
|
|
25981
|
-
const parts = [];
|
|
25982
|
-
for (const key of keys) {
|
|
25983
|
-
parts.push(`${JSON.stringify(key)}:${stableStringify(value[key])}`);
|
|
25984
|
-
}
|
|
25985
|
-
return `{${parts.join(",")}}`;
|
|
25986
|
-
};
|
|
25987
|
-
|
|
25988
|
-
// The item with its relations inline: a relation value stands for what its
|
|
25989
|
-
// signal holds (the child row, the child rows, or nothing), a row reached
|
|
25990
|
-
// twice on the same path is written as its id — the setters accept both.
|
|
25991
|
-
const serializeItem = (item, ancestorSet) => {
|
|
25992
|
-
ancestorSet.add(item);
|
|
25993
|
-
const serialized = {};
|
|
25994
|
-
for (const key of Object.keys(item)) {
|
|
25995
|
-
serialized[key] = serializeValue(item[key], ancestorSet);
|
|
25996
|
-
}
|
|
25997
|
-
ancestorSet.delete(item);
|
|
25998
|
-
return serialized;
|
|
25999
|
-
};
|
|
26000
|
-
const serializeValue = (value, ancestorSet) => {
|
|
26001
|
-
if (value === null || typeof value !== "object") {
|
|
26002
|
-
return value;
|
|
26003
|
-
}
|
|
26004
|
-
if (Array.isArray(value)) {
|
|
26005
|
-
return value.map((entry) => serializeValue(entry, ancestorSet));
|
|
26006
|
-
}
|
|
26007
|
-
const objectSignal = value[SYMBOL_OBJECT_SIGNAL];
|
|
26008
|
-
if (objectSignal) {
|
|
26009
|
-
const held = objectSignal.value;
|
|
26010
|
-
if (held === undefined || held === null) {
|
|
26011
|
-
return null;
|
|
26012
|
-
}
|
|
26013
|
-
return serializeValue(held, ancestorSet);
|
|
26014
|
-
}
|
|
26015
|
-
if (typeof value.toJSON === "function") {
|
|
26016
|
-
return value;
|
|
26017
|
-
}
|
|
26018
|
-
if (ancestorSet.has(value)) {
|
|
26019
|
-
return Object.hasOwn(value, SYMBOL_IDENTITY)
|
|
26020
|
-
? value[SYMBOL_IDENTITY]
|
|
26021
|
-
: null;
|
|
26022
|
-
}
|
|
26023
|
-
return serializeItem(value, ancestorSet);
|
|
26024
|
-
};
|
|
26025
|
-
|
|
26026
26179
|
/*
|
|
26027
26180
|
* GET_RANGE: reading a resource one slice at a time, for a list that draws its
|
|
26028
26181
|
* rows as it goes (`<List.Items itemsAction>`).
|
|
@@ -26059,9 +26212,23 @@ const serializeValue = (value, ancestorSet) => {
|
|
|
26059
26212
|
* of what a rerun is for an action. Every reader made by `bindParams` shares
|
|
26060
26213
|
* the signal and the compositions of the one it comes from: the params say
|
|
26061
26214
|
* which slices are read, not which collection.
|
|
26215
|
+
*
|
|
26216
|
+
* A composition also outlives the document when a list on the page on screen
|
|
26217
|
+
* reads it (see keepPageOnScreen): the ranks around the window it draws are
|
|
26218
|
+
* written with their rows, and the first read of the next document finds them
|
|
26219
|
+
* as a composition left by a list that went away — the same return, after a
|
|
26220
|
+
* longer absence.
|
|
26062
26221
|
*/
|
|
26063
26222
|
|
|
26064
26223
|
|
|
26224
|
+
// The ranks kept of a composition for the next document: the window a list
|
|
26225
|
+
// draws, widened by at least one step on each side, and snapped to steps so a
|
|
26226
|
+
// scroll within one draws the same ranks and writes nothing.
|
|
26227
|
+
const KEPT_RANK_STEP = 20;
|
|
26228
|
+
// Counts the answers every composition took, so that one landing can be told
|
|
26229
|
+
// from one seen before (see readKept).
|
|
26230
|
+
let answerSequence = 0;
|
|
26231
|
+
|
|
26065
26232
|
const createRangeReader = (
|
|
26066
26233
|
actionName,
|
|
26067
26234
|
callback,
|
|
@@ -26070,6 +26237,9 @@ const createRangeReader = (
|
|
|
26070
26237
|
params: boundParams,
|
|
26071
26238
|
invalidationSignal = signal(0),
|
|
26072
26239
|
compositionSet = new Set(),
|
|
26240
|
+
// Bumped whenever what is kept of a composition for the next document
|
|
26241
|
+
// changes (see readKept); shared like the compositions themselves.
|
|
26242
|
+
keptVersionSignal = signal(0),
|
|
26073
26243
|
},
|
|
26074
26244
|
) => {
|
|
26075
26245
|
// Which composition this reader is about: the values its params hold, not its
|
|
@@ -26086,6 +26256,40 @@ const createRangeReader = (
|
|
|
26086
26256
|
}
|
|
26087
26257
|
return null;
|
|
26088
26258
|
};
|
|
26259
|
+
const keptKeyOf = (params) => getKeptReadKey(actionName, params);
|
|
26260
|
+
const bumpKeptVersion = () => {
|
|
26261
|
+
keptVersionSignal.value = keptVersionSignal.peek() + 1;
|
|
26262
|
+
};
|
|
26263
|
+
// The composition the previous document kept for these params, entering the
|
|
26264
|
+
// store and the compositions as if a list had just left it.
|
|
26265
|
+
const restoreKeptComposition = (params) => {
|
|
26266
|
+
const kept = takeKeptRead(keptKeyOf(params));
|
|
26267
|
+
if (!kept || typeof kept.count !== "number" || !kept.items) {
|
|
26268
|
+
return null;
|
|
26269
|
+
}
|
|
26270
|
+
const composition = {
|
|
26271
|
+
params,
|
|
26272
|
+
idByIndex: new Map(),
|
|
26273
|
+
count: kept.count,
|
|
26274
|
+
keptFrom: undefined,
|
|
26275
|
+
keptTo: undefined,
|
|
26276
|
+
answeredAt: 0,
|
|
26277
|
+
};
|
|
26278
|
+
for (const rank of Object.keys(kept.items)) {
|
|
26279
|
+
const index = Number(rank);
|
|
26280
|
+
const item = upsertKeptItem(store, kept.items[rank]);
|
|
26281
|
+
composition.idByIndex.set(index, item[store.idKey]);
|
|
26282
|
+
if (composition.keptFrom === undefined || index < composition.keptFrom) {
|
|
26283
|
+
composition.keptFrom = index;
|
|
26284
|
+
}
|
|
26285
|
+
if (composition.keptTo === undefined || index + 1 > composition.keptTo) {
|
|
26286
|
+
composition.keptTo = index + 1;
|
|
26287
|
+
}
|
|
26288
|
+
}
|
|
26289
|
+
compositionSet.add(composition);
|
|
26290
|
+
bumpKeptVersion();
|
|
26291
|
+
return composition;
|
|
26292
|
+
};
|
|
26089
26293
|
const readRange = async (range = {}) => {
|
|
26090
26294
|
const { signal, ...rangeParams } = range;
|
|
26091
26295
|
const paramsResolved = { ...resolveParams(boundParams), ...rangeParams };
|
|
@@ -26119,11 +26323,15 @@ const createRangeReader = (
|
|
|
26119
26323
|
readRange.invalidate = () => {
|
|
26120
26324
|
compositionSet.clear();
|
|
26121
26325
|
invalidationSignal.value = invalidationSignal.peek() + 1;
|
|
26326
|
+
bumpKeptVersion();
|
|
26122
26327
|
};
|
|
26123
26328
|
// The rows of a composition, drawn from the store: a rank whose row is gone
|
|
26124
26329
|
// from the store resolves to nothing and is asked for again.
|
|
26125
26330
|
readRange.readComposition = () => {
|
|
26126
|
-
const
|
|
26331
|
+
const params = currentParams();
|
|
26332
|
+
const composition = untracked(
|
|
26333
|
+
() => findComposition(params) || restoreKeptComposition(params),
|
|
26334
|
+
);
|
|
26127
26335
|
if (!composition) {
|
|
26128
26336
|
return null;
|
|
26129
26337
|
}
|
|
@@ -26147,18 +26355,85 @@ const createRangeReader = (
|
|
|
26147
26355
|
const params = currentParams();
|
|
26148
26356
|
let composition = findComposition(params);
|
|
26149
26357
|
if (!composition) {
|
|
26150
|
-
composition = {
|
|
26358
|
+
composition = {
|
|
26359
|
+
params,
|
|
26360
|
+
idByIndex: new Map(),
|
|
26361
|
+
count,
|
|
26362
|
+
keptFrom: undefined,
|
|
26363
|
+
keptTo: undefined,
|
|
26364
|
+
answeredAt: 0,
|
|
26365
|
+
};
|
|
26151
26366
|
compositionSet.add(composition);
|
|
26152
26367
|
} else if (replace) {
|
|
26153
26368
|
composition.idByIndex = new Map();
|
|
26154
26369
|
}
|
|
26155
26370
|
composition.count = count;
|
|
26371
|
+
answerSequence++;
|
|
26372
|
+
composition.answeredAt = answerSequence;
|
|
26156
26373
|
for (const [index, item] of byIndex) {
|
|
26157
26374
|
const id = item ? item[store.idKey] : undefined;
|
|
26158
26375
|
if (id !== undefined) {
|
|
26159
26376
|
composition.idByIndex.set(index, id);
|
|
26160
26377
|
}
|
|
26161
26378
|
}
|
|
26379
|
+
bumpKeptVersion();
|
|
26380
|
+
};
|
|
26381
|
+
// The ranks [windowFrom, windowTo) a list draws of this composition: what is
|
|
26382
|
+
// kept of it for the next document is around them (see readKept).
|
|
26383
|
+
readRange.frameComposition = (windowFrom, windowTo) => {
|
|
26384
|
+
const composition = findComposition(currentParams());
|
|
26385
|
+
if (!composition) {
|
|
26386
|
+
return;
|
|
26387
|
+
}
|
|
26388
|
+
let keptFrom =
|
|
26389
|
+
Math.floor((windowFrom - KEPT_RANK_STEP) / KEPT_RANK_STEP) *
|
|
26390
|
+
KEPT_RANK_STEP;
|
|
26391
|
+
if (keptFrom < 0) {
|
|
26392
|
+
keptFrom = 0;
|
|
26393
|
+
}
|
|
26394
|
+
const keptTo =
|
|
26395
|
+
Math.ceil((windowTo + KEPT_RANK_STEP) / KEPT_RANK_STEP) * KEPT_RANK_STEP;
|
|
26396
|
+
if (composition.keptFrom === keptFrom && composition.keptTo === keptTo) {
|
|
26397
|
+
return;
|
|
26398
|
+
}
|
|
26399
|
+
composition.keptFrom = keptFrom;
|
|
26400
|
+
composition.keptTo = keptTo;
|
|
26401
|
+
bumpKeptVersion();
|
|
26402
|
+
};
|
|
26403
|
+
// What a document keeps of the composition these params read, for the
|
|
26404
|
+
// next one (see keepPageOnScreen): the count, and the ranks around the
|
|
26405
|
+
// window last drawn with their rows inline. Read inside an effect, it
|
|
26406
|
+
// follows the composition and the rows. `answeredAt` grows with every answer
|
|
26407
|
+
// the composition takes (0: none in this document, restored from a kept one).
|
|
26408
|
+
readRange.readKept = () => {
|
|
26409
|
+
// eslint-disable-next-line no-unused-expressions
|
|
26410
|
+
keptVersionSignal.value;
|
|
26411
|
+
const params = resolveParams(boundParams);
|
|
26412
|
+
const key = keptKeyOf(params);
|
|
26413
|
+
const composition = findComposition(params);
|
|
26414
|
+
if (!composition || composition.count === undefined) {
|
|
26415
|
+
return { key, answeredAt: 0, readEntry: () => undefined };
|
|
26416
|
+
}
|
|
26417
|
+
const readEntry = () => {
|
|
26418
|
+
const items = {};
|
|
26419
|
+
for (const [index, id] of composition.idByIndex) {
|
|
26420
|
+
if (
|
|
26421
|
+
composition.keptFrom !== undefined &&
|
|
26422
|
+
index < composition.keptFrom
|
|
26423
|
+
) {
|
|
26424
|
+
continue;
|
|
26425
|
+
}
|
|
26426
|
+
if (composition.keptTo !== undefined && index >= composition.keptTo) {
|
|
26427
|
+
continue;
|
|
26428
|
+
}
|
|
26429
|
+
const item = store.select(id);
|
|
26430
|
+
if (item) {
|
|
26431
|
+
items[index] = serializeItem(item, new Set());
|
|
26432
|
+
}
|
|
26433
|
+
}
|
|
26434
|
+
return { count: composition.count, items };
|
|
26435
|
+
};
|
|
26436
|
+
return { key, answeredAt: composition.answeredAt, readEntry };
|
|
26162
26437
|
};
|
|
26163
26438
|
// The same trade a list makes with the rows it holds, applied to what is kept
|
|
26164
26439
|
// for the next mount. Two lists on one collection each trim by their own
|
|
@@ -26169,11 +26444,16 @@ const createRangeReader = (
|
|
|
26169
26444
|
if (!composition || composition.idByIndex.size <= budget) {
|
|
26170
26445
|
return;
|
|
26171
26446
|
}
|
|
26447
|
+
let trimmed = false;
|
|
26172
26448
|
for (const index of composition.idByIndex.keys()) {
|
|
26173
26449
|
if (index < keepFrom || index > keepTo) {
|
|
26174
26450
|
composition.idByIndex.delete(index);
|
|
26451
|
+
trimmed = true;
|
|
26175
26452
|
}
|
|
26176
26453
|
}
|
|
26454
|
+
if (trimmed) {
|
|
26455
|
+
bumpKeptVersion();
|
|
26456
|
+
}
|
|
26177
26457
|
};
|
|
26178
26458
|
// Memoized for the reasons an action's bindParams is (see actions.js): params
|
|
26179
26459
|
// and the reader they make have synchronized lifetimes, and params equal in
|
|
@@ -26189,6 +26469,7 @@ const createRangeReader = (
|
|
|
26189
26469
|
params: boundParams ? { ...boundParams, ...paramsToBind } : paramsToBind,
|
|
26190
26470
|
invalidationSignal,
|
|
26191
26471
|
compositionSet,
|
|
26472
|
+
keptVersionSignal,
|
|
26192
26473
|
});
|
|
26193
26474
|
readerByParams.set(paramsToBind, reader);
|
|
26194
26475
|
return reader;
|
|
@@ -27623,7 +27904,7 @@ const createRestActionFactoryForRoot = (
|
|
|
27623
27904
|
`${name}.${verb}`,
|
|
27624
27905
|
declarationSite,
|
|
27625
27906
|
);
|
|
27626
|
-
|
|
27907
|
+
const action = createAction(callback, {
|
|
27627
27908
|
name: `${name}.${verb}`,
|
|
27628
27909
|
meta: { verb, isMany: false, paramScope },
|
|
27629
27910
|
resultToValue: (result, action) => {
|
|
@@ -27659,17 +27940,33 @@ const createRestActionFactoryForRoot = (
|
|
|
27659
27940
|
},
|
|
27660
27941
|
valueToData: (itemId) => store.select(itemId),
|
|
27661
27942
|
// While the request is out, the row the store already holds for these
|
|
27662
|
-
// params is drawn
|
|
27943
|
+
// params is drawn, or the row the page kept from the previous document
|
|
27944
|
+
// (kept_reads.js): the action starts on the "refresh over a known
|
|
27663
27945
|
// answer" line of data_states.md rather than the first-load one.
|
|
27664
27946
|
provisionalValue:
|
|
27665
27947
|
verb === "GET"
|
|
27666
|
-
? (params) => {
|
|
27667
|
-
const item = untracked(() =>
|
|
27948
|
+
? (params, action) => {
|
|
27949
|
+
const item = untracked(() => {
|
|
27950
|
+
const itemForGet = findItemForGet(params);
|
|
27951
|
+
if (itemForGet) {
|
|
27952
|
+
return itemForGet;
|
|
27953
|
+
}
|
|
27954
|
+
const keptItem = takeKeptRead(
|
|
27955
|
+
getKeptReadKey(action.name, params),
|
|
27956
|
+
);
|
|
27957
|
+
return keptItem ? upsertKeptItem(store, keptItem) : null;
|
|
27958
|
+
});
|
|
27668
27959
|
return item ? item[idKey] : undefined;
|
|
27669
27960
|
}
|
|
27670
27961
|
: undefined,
|
|
27962
|
+
// Another params' answer is another row.
|
|
27963
|
+
inheritData: verb !== "GET",
|
|
27671
27964
|
completeSideEffect: onActionComplete,
|
|
27672
27965
|
});
|
|
27966
|
+
if (verb === "GET") {
|
|
27967
|
+
markKeepableRead(action);
|
|
27968
|
+
}
|
|
27969
|
+
return action;
|
|
27673
27970
|
};
|
|
27674
27971
|
const createActionAffectingManyItems = (
|
|
27675
27972
|
verb,
|
|
@@ -27684,7 +27981,7 @@ const createRestActionFactoryForRoot = (
|
|
|
27684
27981
|
return itemArray.map((item) => item[idKey]);
|
|
27685
27982
|
};
|
|
27686
27983
|
|
|
27687
|
-
|
|
27984
|
+
const action = createAction(callback, {
|
|
27688
27985
|
meta: { verb, isMany: true, paramScope },
|
|
27689
27986
|
name: `${name}.${verb}_MANY`,
|
|
27690
27987
|
resultToValue: applyResultToValue,
|
|
@@ -27692,6 +27989,25 @@ const createRestActionFactoryForRoot = (
|
|
|
27692
27989
|
// undefined so a screen can tell "not asked yet" from "answered: none".
|
|
27693
27990
|
valueToData: (idArray) =>
|
|
27694
27991
|
idArray === undefined ? undefined : store.selectAll(idArray),
|
|
27992
|
+
// The rows the page kept from the previous document (kept_reads.js),
|
|
27993
|
+
// drawn while the request is out. A binding moving to new params still
|
|
27994
|
+
// hands over the previous answer when there are none: a list standing
|
|
27995
|
+
// in for the next one beats a skeleton.
|
|
27996
|
+
provisionalValue:
|
|
27997
|
+
verb === "GET"
|
|
27998
|
+
? (params, action) =>
|
|
27999
|
+
untracked(() => {
|
|
28000
|
+
const keptItems = takeKeptRead(
|
|
28001
|
+
getKeptReadKey(action.name, params),
|
|
28002
|
+
);
|
|
28003
|
+
if (!Array.isArray(keptItems)) {
|
|
28004
|
+
return undefined;
|
|
28005
|
+
}
|
|
28006
|
+
return keptItems.map(
|
|
28007
|
+
(keptItem) => upsertKeptItem(store, keptItem)[idKey],
|
|
28008
|
+
);
|
|
28009
|
+
})
|
|
28010
|
+
: undefined,
|
|
27695
28011
|
completeSideEffect: (actionCompleted) => {
|
|
27696
28012
|
onActionComplete(actionCompleted);
|
|
27697
28013
|
if (
|
|
@@ -27707,6 +28023,10 @@ const createRestActionFactoryForRoot = (
|
|
|
27707
28023
|
return syncIdArrayOnRename(store, idKey, actionCompleted.valueSignal);
|
|
27708
28024
|
},
|
|
27709
28025
|
});
|
|
28026
|
+
if (verb === "GET") {
|
|
28027
|
+
markKeepableRead(action);
|
|
28028
|
+
}
|
|
28029
|
+
return action;
|
|
27710
28030
|
};
|
|
27711
28031
|
|
|
27712
28032
|
return createActionForRoot;
|
|
@@ -27717,10 +28037,12 @@ const createRestActionFactoryForRoot = (
|
|
|
27717
28037
|
// NetworkPolicyError carrying the policy's reason. A read is called or not depending
|
|
27718
28038
|
// on where the policy answers reads from. Answered from the store, a GET of a
|
|
27719
28039
|
// root resource completes with the row the store holds for it (see
|
|
27720
|
-
// findItemInStore)
|
|
27721
|
-
//
|
|
27722
|
-
//
|
|
27723
|
-
//
|
|
28040
|
+
// findItemInStore), and a root GET or GET_MANY holding rows for these params
|
|
28041
|
+
// (the kept page, drawn while the request is out) with those — handing them
|
|
28042
|
+
// back is an upsert without effect, so the action completes with what it had
|
|
28043
|
+
// and nothing is asked. A relationship GET has no row of its own to answer
|
|
28044
|
+
// with and settles with the same error. A completed GET asked to rerun never
|
|
28045
|
+
// gets here (actions.js holds it).
|
|
27724
28046
|
const applyNetworkPolicy = (
|
|
27725
28047
|
restCallback,
|
|
27726
28048
|
{ verb, isMany, findItemInStore },
|
|
@@ -27734,6 +28056,12 @@ const applyNetworkPolicy = (
|
|
|
27734
28056
|
if (!policy.readsFromStore) {
|
|
27735
28057
|
return restCallback(params, context);
|
|
27736
28058
|
}
|
|
28059
|
+
if (isKeepableRead(context.action)) {
|
|
28060
|
+
const dataHeld = context.action.dataSignal.peek();
|
|
28061
|
+
if (dataHeld !== undefined) {
|
|
28062
|
+
return dataHeld;
|
|
28063
|
+
}
|
|
28064
|
+
}
|
|
27737
28065
|
if (!isMany && findItemInStore) {
|
|
27738
28066
|
const item = findItemInStore(params, context.action);
|
|
27739
28067
|
if (item) {
|
|
@@ -28261,24 +28589,6 @@ const TYPE_CONVERTERS = {
|
|
|
28261
28589
|
},
|
|
28262
28590
|
};
|
|
28263
28591
|
|
|
28264
|
-
/**
|
|
28265
|
-
* What a component can learn about the page it is rendered in, from the route
|
|
28266
|
-
* container rendering that page (see useContentKeepingLeavingPages in
|
|
28267
|
-
* route.jsx). `null` outside of any route container.
|
|
28268
|
-
*
|
|
28269
|
-
* - `isShown()` — whether the container would still show the page. False as
|
|
28270
|
-
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
28271
|
-
* hidden until the route transition leaving it is over, and nothing it reads
|
|
28272
|
-
* is worth a render any more. False for good once it is kept while leaving: a
|
|
28273
|
-
* return to its route mounts the page anew. Asked outside any render — by a
|
|
28274
|
-
* subscription deciding whether to re-render — and it reads the routes
|
|
28275
|
-
* untracked, so asking subscribes nobody to them.
|
|
28276
|
-
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
28277
|
-
* plays: still mounted, so what it fills outside its own nodes (a slot in a
|
|
28278
|
-
* bar) steps out rather than showing with the page arriving.
|
|
28279
|
-
*/
|
|
28280
|
-
const RoutePageContext = createContext(null);
|
|
28281
|
-
|
|
28282
28592
|
const promiseStateWeakMap = new WeakMap();
|
|
28283
28593
|
const usePromiseAsyncData = (
|
|
28284
28594
|
promise,
|
|
@@ -33120,11 +33430,17 @@ const useContentKeepingLeavingPages = (branch, readShownBranch, parentPage) => {
|
|
|
33120
33430
|
}
|
|
33121
33431
|
}
|
|
33122
33432
|
});
|
|
33433
|
+
// Also run when a Suspense boundary above parks this container — a page
|
|
33434
|
+
// arriving that suspends to a `<Loading>` around both pages: every hook of
|
|
33435
|
+
// the pages kept while leaving has then been cleaned up, which is all that
|
|
33436
|
+
// keeping them saved. Dropped, so the render resuming the container takes
|
|
33437
|
+
// them down, rather than re-running every effect of a hidden page.
|
|
33123
33438
|
useLayoutEffect(() => {
|
|
33124
33439
|
return () => {
|
|
33125
33440
|
for (const entry of leavingRef.current) {
|
|
33126
33441
|
entry.hold.containers.delete(rerender);
|
|
33127
33442
|
}
|
|
33443
|
+
leavingRef.current = [];
|
|
33128
33444
|
};
|
|
33129
33445
|
}, []);
|
|
33130
33446
|
if (leaving.length === 0) {
|
|
@@ -37896,6 +38212,37 @@ const writeCover = (edge, element, size) => {
|
|
|
37896
38212
|
style.setProperty(property, value);
|
|
37897
38213
|
};
|
|
37898
38214
|
|
|
38215
|
+
// Every route action, with whether it asks something of the address at the
|
|
38216
|
+
// moment: its route matches and its params getter returns something.
|
|
38217
|
+
const routeActionEntriesSignal = signal([]);
|
|
38218
|
+
let activeRouteActionsPrevious = new Set();
|
|
38219
|
+
/**
|
|
38220
|
+
* The route actions asking something for the page on screen: their route
|
|
38221
|
+
* matches and their params getter returns something — the running ones, not
|
|
38222
|
+
* every matching one, so an action bound to every page that asks only when a
|
|
38223
|
+
* popup is open over it counts while it is open. Read to rerun what the page
|
|
38224
|
+
* reads (the app back from the background), and by `keepPageOnScreen`.
|
|
38225
|
+
* The set is handed out again only when its members change.
|
|
38226
|
+
*
|
|
38227
|
+
* A page's code is declared as a route action too; `action.meta.verb` says
|
|
38228
|
+
* which ones read data.
|
|
38229
|
+
*
|
|
38230
|
+
* @type {import("@preact/signals").ReadonlySignal<Set<object>>}
|
|
38231
|
+
*/
|
|
38232
|
+
const activeRouteActionsSignal = computed(() => {
|
|
38233
|
+
const active = new Set();
|
|
38234
|
+
for (const { routeAction, askingSignal } of routeActionEntriesSignal.value) {
|
|
38235
|
+
if (askingSignal.value) {
|
|
38236
|
+
active.add(routeAction);
|
|
38237
|
+
}
|
|
38238
|
+
}
|
|
38239
|
+
if (haveSameMembers(active, activeRouteActionsPrevious)) {
|
|
38240
|
+
return activeRouteActionsPrevious;
|
|
38241
|
+
}
|
|
38242
|
+
activeRouteActionsPrevious = active;
|
|
38243
|
+
return active;
|
|
38244
|
+
});
|
|
38245
|
+
|
|
37899
38246
|
/**
|
|
37900
38247
|
* Binds an action to a route: it runs when the route matches, with the params
|
|
37901
38248
|
* the effect reads off the address, runs again on a `reload()`, and is aborted
|
|
@@ -37953,6 +38300,13 @@ const routeAction = (
|
|
|
37953
38300
|
return params;
|
|
37954
38301
|
};
|
|
37955
38302
|
const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
|
|
38303
|
+
routeActionEntriesSignal.value = [
|
|
38304
|
+
...routeActionEntriesSignal.peek(),
|
|
38305
|
+
{
|
|
38306
|
+
routeAction: actionBoundToRoute,
|
|
38307
|
+
askingSignal: computed(() => Boolean(readParamsAsked())),
|
|
38308
|
+
},
|
|
38309
|
+
];
|
|
37956
38310
|
for (const route of routes) {
|
|
37957
38311
|
// Asked of the route's params as they are, like the effect asks them.
|
|
37958
38312
|
registerRouteReload(route, () =>
|
|
@@ -38036,6 +38390,343 @@ const anyMatchingRouteSignal = (routes) => {
|
|
|
38036
38390
|
return anyMatchingSignal;
|
|
38037
38391
|
};
|
|
38038
38392
|
|
|
38393
|
+
const haveSameMembers = (setA, setB) => {
|
|
38394
|
+
if (setA.size !== setB.size) {
|
|
38395
|
+
return false;
|
|
38396
|
+
}
|
|
38397
|
+
for (const value of setA) {
|
|
38398
|
+
if (!setB.has(value)) {
|
|
38399
|
+
return false;
|
|
38400
|
+
}
|
|
38401
|
+
}
|
|
38402
|
+
return true;
|
|
38403
|
+
};
|
|
38404
|
+
|
|
38405
|
+
/*
|
|
38406
|
+
* The page on screen, kept for the next document: what its reads answered,
|
|
38407
|
+
* written into a signal the app hands over, and drawn again by the same reads
|
|
38408
|
+
* in a document opening on the same page (a reload, a tab the system
|
|
38409
|
+
* discarded). The read side is state/rest/kept_reads.js; this is what decides
|
|
38410
|
+
* which reads are the page, and when the slot is written.
|
|
38411
|
+
*
|
|
38412
|
+
* The unit is the page, not the resource. One resource is read by several
|
|
38413
|
+
* screens — a search, someone's games, a game opened from anywhere — and kept
|
|
38414
|
+
* per resource it would keep every row ever opened. The page's reads are what
|
|
38415
|
+
* navi already runs for it: the route actions asking something for the page
|
|
38416
|
+
* (activeRouteActionsSignal) and the compositions the lists on screen read
|
|
38417
|
+
* (shownRangeReadersSignal). A search typed on the page is neither, and is not
|
|
38418
|
+
* kept.
|
|
38419
|
+
*
|
|
38420
|
+
* A read is written once it has answered in this document. Before that it
|
|
38421
|
+
* keeps what the previous document kept for it, never what it shows meanwhile:
|
|
38422
|
+
* an answer handed over from other params (a list standing in for the next
|
|
38423
|
+
* one) says nothing about these.
|
|
38424
|
+
*
|
|
38425
|
+
* The write waits for the page to settle and is flushed when the document is
|
|
38426
|
+
* hidden. A navigation goes through pages half there — the route matched, its
|
|
38427
|
+
* list not mounted yet, the page left still drawn under a transition — and
|
|
38428
|
+
* each would otherwise be written out in full. Hidden is the moment the system
|
|
38429
|
+
* may discard the document, so nothing is lost by waiting until then.
|
|
38430
|
+
*
|
|
38431
|
+
* The signal turning `undefined` from outside (sign-out) forgets the page, and
|
|
38432
|
+
* nothing is written until a read lands again: written at once, the page still
|
|
38433
|
+
* on screen would put back the account just left.
|
|
38434
|
+
*/
|
|
38435
|
+
|
|
38436
|
+
const WRITE_DELAY = 1000;
|
|
38437
|
+
const NOTHING_PENDING = {};
|
|
38438
|
+
let stopKeepingCurrent = null;
|
|
38439
|
+
|
|
38440
|
+
/**
|
|
38441
|
+
* Keeps what the page on screen reads, so that a new document opening on it —
|
|
38442
|
+
* a reload, a pull-to-refresh, a tab the system discarded in the background —
|
|
38443
|
+
* draws it again while its reads go out, rather than skeletons.
|
|
38444
|
+
*
|
|
38445
|
+
* What the page reads is what navi runs for it: the route actions asking
|
|
38446
|
+
* something for it (see `activeRouteActionsSignal`) whose action is a resource
|
|
38447
|
+
* `GET` or `GET_MANY`, and the `GET_RANGE` compositions the `<List.Items>` on
|
|
38448
|
+
* screen read, around the window they draw. A navigation replaces the slot with
|
|
38449
|
+
* the next page's reads, and the page left is dropped. In the next document,
|
|
38450
|
+
* the first run of the same read with the same params draws the kept answer as
|
|
38451
|
+
* its provisional value: `data` set while `loading` is `true`, the answer
|
|
38452
|
+
* replacing it. Under a network policy answering reads from the store, the
|
|
38453
|
+
* kept answer answers the read. The copy follows the store: a `PUT` on a row
|
|
38454
|
+
* rewrites it.
|
|
38455
|
+
*
|
|
38456
|
+
* Call it before the routes start: the first runs are the ones that look.
|
|
38457
|
+
*
|
|
38458
|
+
* @param {object} options
|
|
38459
|
+
* @param {import("@preact/signals").Signal} options.signal - where the slot is
|
|
38460
|
+
* written: a `stateSignal` with `persists: true` and `type: "object"`, its
|
|
38461
|
+
* `id` holding whatever makes a kept page unusable (the deployed version).
|
|
38462
|
+
* Writing `undefined` into it (sign-out) forgets the page, and nothing is
|
|
38463
|
+
* written again until a read lands.
|
|
38464
|
+
* @param {() => boolean} [options.when] - read whenever the slot is about to be
|
|
38465
|
+
* read or written: `false` reads nothing, writes nothing, and empties the
|
|
38466
|
+
* signal (a tab viewing as someone else). A function, so it can read a signal.
|
|
38467
|
+
* @param {object[]} [options.always] - routes whose reads stay kept once the
|
|
38468
|
+
* page is left, until it is visited again: the page an app opens on when it
|
|
38469
|
+
* is launched rather than reloaded.
|
|
38470
|
+
* @returns {() => void} stops keeping the page.
|
|
38471
|
+
* @see docs/resource.md — what is kept and what is not, what stays the app's
|
|
38472
|
+
*/
|
|
38473
|
+
const keepPageOnScreen = ({
|
|
38474
|
+
signal: keptSignal,
|
|
38475
|
+
when,
|
|
38476
|
+
always = [],
|
|
38477
|
+
} = {}) => {
|
|
38478
|
+
if (!isSignal(keptSignal)) {
|
|
38479
|
+
throw new TypeError(
|
|
38480
|
+
`keepPageOnScreen() needs a signal to write the page into, received ${keptSignal}`,
|
|
38481
|
+
);
|
|
38482
|
+
}
|
|
38483
|
+
if (stopKeepingCurrent) {
|
|
38484
|
+
stopKeepingCurrent();
|
|
38485
|
+
}
|
|
38486
|
+
const isAllowed = () => {
|
|
38487
|
+
return when ? Boolean(when()) : true;
|
|
38488
|
+
};
|
|
38489
|
+
const alwaysMatchingSignal = anyMatchingRouteSignal(always);
|
|
38490
|
+
|
|
38491
|
+
// What the signal held when it was read: the previous document's page.
|
|
38492
|
+
let loadedEntryMap = new Map();
|
|
38493
|
+
let loadedAlwaysKeys = [];
|
|
38494
|
+
// Which reads answered in this document (since the last forget): only
|
|
38495
|
+
// those write what they show.
|
|
38496
|
+
let answeredActionWeakSet = new WeakSet();
|
|
38497
|
+
let answeredRangeKeySet = new Set();
|
|
38498
|
+
// The state each action was last seen in, and the last answer seen of each
|
|
38499
|
+
// range reader: a read lands when they move, and not because the slot looks
|
|
38500
|
+
// again at something that had answered before a forget.
|
|
38501
|
+
const stateSeenWeakMap = new WeakMap();
|
|
38502
|
+
const answeredAtSeenWeakMap = new WeakMap();
|
|
38503
|
+
// Forgotten: nothing is written until a read lands.
|
|
38504
|
+
let paused = false;
|
|
38505
|
+
// The reads of the always-kept page, as they were the last time it was on
|
|
38506
|
+
// screen in this document; null until then.
|
|
38507
|
+
let alwaysReads = null;
|
|
38508
|
+
|
|
38509
|
+
const forget = () => {
|
|
38510
|
+
loadedEntryMap = new Map();
|
|
38511
|
+
loadedAlwaysKeys = [];
|
|
38512
|
+
answeredActionWeakSet = new WeakSet();
|
|
38513
|
+
answeredRangeKeySet = new Set();
|
|
38514
|
+
alwaysReads = null;
|
|
38515
|
+
paused = true;
|
|
38516
|
+
withdrawKeptReads();
|
|
38517
|
+
};
|
|
38518
|
+
|
|
38519
|
+
let lastWritten;
|
|
38520
|
+
let pendingWrite = NOTHING_PENDING;
|
|
38521
|
+
let writeTimeout;
|
|
38522
|
+
const flush = () => {
|
|
38523
|
+
clearTimeout(writeTimeout);
|
|
38524
|
+
if (pendingWrite === NOTHING_PENDING) {
|
|
38525
|
+
return;
|
|
38526
|
+
}
|
|
38527
|
+
const value = pendingWrite;
|
|
38528
|
+
pendingWrite = NOTHING_PENDING;
|
|
38529
|
+
if (compareTwoJsValues(value, lastWritten)) {
|
|
38530
|
+
return;
|
|
38531
|
+
}
|
|
38532
|
+
lastWritten = value;
|
|
38533
|
+
keptSignal.value = value;
|
|
38534
|
+
};
|
|
38535
|
+
const scheduleWrite = (value) => {
|
|
38536
|
+
pendingWrite = value;
|
|
38537
|
+
clearTimeout(writeTimeout);
|
|
38538
|
+
writeTimeout = setTimeout(flush, WRITE_DELAY);
|
|
38539
|
+
};
|
|
38540
|
+
const onVisibilityChange = () => {
|
|
38541
|
+
if (document.visibilityState === "hidden") {
|
|
38542
|
+
flush();
|
|
38543
|
+
}
|
|
38544
|
+
};
|
|
38545
|
+
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
38546
|
+
window.addEventListener("pagehide", flush);
|
|
38547
|
+
|
|
38548
|
+
// Runs at once with what the signal already holds (the slot read from
|
|
38549
|
+
// storage), then on every write — the slot's own ones are recognized and
|
|
38550
|
+
// left alone.
|
|
38551
|
+
const unsubscribe = keptSignal.subscribe((value) => {
|
|
38552
|
+
if (value === lastWritten) {
|
|
38553
|
+
return;
|
|
38554
|
+
}
|
|
38555
|
+
lastWritten = value;
|
|
38556
|
+
pendingWrite = NOTHING_PENDING;
|
|
38557
|
+
clearTimeout(writeTimeout);
|
|
38558
|
+
if (
|
|
38559
|
+
value === undefined ||
|
|
38560
|
+
value === null ||
|
|
38561
|
+
typeof value !== "object" ||
|
|
38562
|
+
!isAllowed()
|
|
38563
|
+
) {
|
|
38564
|
+
forget();
|
|
38565
|
+
return;
|
|
38566
|
+
}
|
|
38567
|
+
const entries =
|
|
38568
|
+
value.entries && typeof value.entries === "object" ? value.entries : {};
|
|
38569
|
+
loadedEntryMap = new Map(Object.entries(entries));
|
|
38570
|
+
loadedAlwaysKeys = Array.isArray(value.always)
|
|
38571
|
+
? value.always.filter((key) => loadedEntryMap.has(key))
|
|
38572
|
+
: [];
|
|
38573
|
+
alwaysReads = null;
|
|
38574
|
+
paused = false;
|
|
38575
|
+
offerKeptReads(loadedEntryMap);
|
|
38576
|
+
});
|
|
38577
|
+
|
|
38578
|
+
const actionReadWeakMap = new WeakMap();
|
|
38579
|
+
const actionReadOf = (action) => {
|
|
38580
|
+
let read = actionReadWeakMap.get(action);
|
|
38581
|
+
if (read) {
|
|
38582
|
+
return read;
|
|
38583
|
+
}
|
|
38584
|
+
read = () => {
|
|
38585
|
+
const state = action.runningStateSignal.value;
|
|
38586
|
+
const stateSeen = stateSeenWeakMap.get(action);
|
|
38587
|
+
stateSeenWeakMap.set(action, state);
|
|
38588
|
+
const landed = state === COMPLETED && stateSeen !== COMPLETED;
|
|
38589
|
+
if (landed) {
|
|
38590
|
+
answeredActionWeakSet.add(action);
|
|
38591
|
+
}
|
|
38592
|
+
return {
|
|
38593
|
+
key: getKeptReadKey(action.name, action.params),
|
|
38594
|
+
answered: answeredActionWeakSet.has(action),
|
|
38595
|
+
landed,
|
|
38596
|
+
readEntry: () => {
|
|
38597
|
+
const data = action.dataSignal.value;
|
|
38598
|
+
if (data === undefined || data === null) {
|
|
38599
|
+
return undefined;
|
|
38600
|
+
}
|
|
38601
|
+
return serializeData(data);
|
|
38602
|
+
},
|
|
38603
|
+
};
|
|
38604
|
+
};
|
|
38605
|
+
actionReadWeakMap.set(action, read);
|
|
38606
|
+
return read;
|
|
38607
|
+
};
|
|
38608
|
+
const rangeReadWeakMap = new WeakMap();
|
|
38609
|
+
const rangeReadOf = (reader) => {
|
|
38610
|
+
let read = rangeReadWeakMap.get(reader);
|
|
38611
|
+
if (read) {
|
|
38612
|
+
return read;
|
|
38613
|
+
}
|
|
38614
|
+
read = () => {
|
|
38615
|
+
const { key, answeredAt, readEntry } = reader.readKept();
|
|
38616
|
+
const answeredAtSeen = answeredAtSeenWeakMap.get(reader) || 0;
|
|
38617
|
+
answeredAtSeenWeakMap.set(reader, answeredAt);
|
|
38618
|
+
const landed = answeredAt > answeredAtSeen;
|
|
38619
|
+
if (landed) {
|
|
38620
|
+
answeredRangeKeySet.add(key);
|
|
38621
|
+
}
|
|
38622
|
+
return {
|
|
38623
|
+
key,
|
|
38624
|
+
answered: answeredRangeKeySet.has(key),
|
|
38625
|
+
landed,
|
|
38626
|
+
readEntry,
|
|
38627
|
+
};
|
|
38628
|
+
};
|
|
38629
|
+
rangeReadWeakMap.set(reader, read);
|
|
38630
|
+
return read;
|
|
38631
|
+
};
|
|
38632
|
+
const readsOnScreen = () => {
|
|
38633
|
+
const reads = [];
|
|
38634
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
38635
|
+
// The instance is what is kept, so that a page left keeps what it read.
|
|
38636
|
+
// The binding's callSource names that instance and is rewritten once the
|
|
38637
|
+
// binding has moved to another one: read, it brings the slot along.
|
|
38638
|
+
// eslint-disable-next-line no-unused-expressions
|
|
38639
|
+
routeAction.callSource;
|
|
38640
|
+
const action = getActionPrivateProperties(routeAction).currentAction;
|
|
38641
|
+
if (action.params === NO_PARAMS || !isKeepableRead(action)) {
|
|
38642
|
+
continue;
|
|
38643
|
+
}
|
|
38644
|
+
reads.push(actionReadOf(action));
|
|
38645
|
+
}
|
|
38646
|
+
for (const reader of shownRangeReadersSignal.value.keys()) {
|
|
38647
|
+
reads.push(rangeReadOf(reader));
|
|
38648
|
+
}
|
|
38649
|
+
return reads;
|
|
38650
|
+
};
|
|
38651
|
+
|
|
38652
|
+
const stopEffect = effect(() => {
|
|
38653
|
+
const allowed = isAllowed();
|
|
38654
|
+
const routed = routedSignal.value;
|
|
38655
|
+
const alwaysMatching = alwaysMatchingSignal.value;
|
|
38656
|
+
if (!allowed) {
|
|
38657
|
+
forget();
|
|
38658
|
+
pendingWrite = undefined;
|
|
38659
|
+
flush();
|
|
38660
|
+
return;
|
|
38661
|
+
}
|
|
38662
|
+
if (!routed) {
|
|
38663
|
+
// No page yet: the slot is what the first reads will draw.
|
|
38664
|
+
return;
|
|
38665
|
+
}
|
|
38666
|
+
const screenReads = readsOnScreen();
|
|
38667
|
+
if (alwaysMatching) {
|
|
38668
|
+
alwaysReads = screenReads;
|
|
38669
|
+
}
|
|
38670
|
+
const entries = {};
|
|
38671
|
+
let landed = false;
|
|
38672
|
+
const collect = (reads) => {
|
|
38673
|
+
const keys = [];
|
|
38674
|
+
for (const read of reads) {
|
|
38675
|
+
const result = read();
|
|
38676
|
+
if (result.landed) {
|
|
38677
|
+
landed = true;
|
|
38678
|
+
}
|
|
38679
|
+
const entry = result.answered
|
|
38680
|
+
? result.readEntry()
|
|
38681
|
+
: loadedEntryMap.get(result.key);
|
|
38682
|
+
if (entry === undefined) {
|
|
38683
|
+
continue;
|
|
38684
|
+
}
|
|
38685
|
+
entries[result.key] = entry;
|
|
38686
|
+
keys.push(result.key);
|
|
38687
|
+
}
|
|
38688
|
+
return keys;
|
|
38689
|
+
};
|
|
38690
|
+
const screenKeys = collect(screenReads);
|
|
38691
|
+
let alwaysKeys;
|
|
38692
|
+
if (alwaysReads === screenReads) {
|
|
38693
|
+
alwaysKeys = screenKeys;
|
|
38694
|
+
} else if (alwaysReads) {
|
|
38695
|
+
alwaysKeys = collect(alwaysReads);
|
|
38696
|
+
} else {
|
|
38697
|
+
alwaysKeys = loadedAlwaysKeys;
|
|
38698
|
+
for (const key of alwaysKeys) {
|
|
38699
|
+
entries[key] = loadedEntryMap.get(key);
|
|
38700
|
+
}
|
|
38701
|
+
}
|
|
38702
|
+
if (paused) {
|
|
38703
|
+
if (!landed) {
|
|
38704
|
+
return;
|
|
38705
|
+
}
|
|
38706
|
+
paused = false;
|
|
38707
|
+
}
|
|
38708
|
+
if (screenKeys.length === 0 && alwaysKeys.length === 0) {
|
|
38709
|
+
scheduleWrite(undefined);
|
|
38710
|
+
return;
|
|
38711
|
+
}
|
|
38712
|
+
scheduleWrite({ entries, screen: screenKeys, always: alwaysKeys });
|
|
38713
|
+
});
|
|
38714
|
+
|
|
38715
|
+
const stop = () => {
|
|
38716
|
+
flush();
|
|
38717
|
+
stopEffect();
|
|
38718
|
+
unsubscribe();
|
|
38719
|
+
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
38720
|
+
window.removeEventListener("pagehide", flush);
|
|
38721
|
+
withdrawKeptReads();
|
|
38722
|
+
if (stopKeepingCurrent === stop) {
|
|
38723
|
+
stopKeepingCurrent = null;
|
|
38724
|
+
}
|
|
38725
|
+
};
|
|
38726
|
+
stopKeepingCurrent = stop;
|
|
38727
|
+
return stop;
|
|
38728
|
+
};
|
|
38729
|
+
|
|
38039
38730
|
/**
|
|
38040
38731
|
* A popup that is the anchor, brought to the front.
|
|
38041
38732
|
*
|
|
@@ -65108,7 +65799,7 @@ const css$E = /* css */`
|
|
|
65108
65799
|
/* A plain div, unlike dialogEl itself (a real <dialog>, natively hidden
|
|
65109
65800
|
by default until .show()/.showModal() adds [open]) — needs its own
|
|
65110
65801
|
starting-hidden mechanism. [navi-hidden] is set from useDialogProps'
|
|
65111
|
-
own backdropProps (recomputed from openController.
|
|
65802
|
+
own backdropProps (recomputed from openController.openedInDom on every
|
|
65112
65803
|
render, present from the very first one), then toggled by plain
|
|
65113
65804
|
removeAttribute/setAttribute in openEffect/close, never an explicit
|
|
65114
65805
|
display override — removing the attribute just lets this rule stop
|
|
@@ -66709,7 +67400,7 @@ const useDialogProps = props => {
|
|
|
66709
67400
|
"navi-out-of-flow": "",
|
|
66710
67401
|
"baseClassName": "navi_dialog_backdrop",
|
|
66711
67402
|
"aria-hidden": "true",
|
|
66712
|
-
// Recomputed fresh on every render from openController.
|
|
67403
|
+
// Recomputed fresh on every render from openController.openedInDom (not
|
|
66713
67404
|
// driven through a mount-time layout effect, unlike this file's own
|
|
66714
67405
|
// imperative open/close toggling below) — present in the DOM
|
|
66715
67406
|
// synchronously from the very first commit, matching this file's own
|
|
@@ -66719,7 +67410,7 @@ const useDialogProps = props => {
|
|
|
66719
67410
|
// needs — see popover.jsx's own identical prop for the full reasoning.
|
|
66720
67411
|
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
66721
67412
|
// Present from this very first render (recomputed fresh on every one
|
|
66722
|
-
// from openController.
|
|
67413
|
+
// from openController.openedInDom, not a frozen mount-time constant) so
|
|
66723
67414
|
// there's no gap for the browser to ever paint this plain-div backdrop
|
|
66724
67415
|
// visible before anything has actually opened it — see popover.jsx's
|
|
66725
67416
|
// own identical prop for the full reasoning, and this file's own CSS
|
|
@@ -66739,7 +67430,7 @@ const useDialogProps = props => {
|
|
|
66739
67430
|
// (kept once, not repeated here).
|
|
66740
67431
|
"aria-expanded": openController.openedInDom ? "true" : "false",
|
|
66741
67432
|
// Present from the very first render (recomputed fresh from
|
|
66742
|
-
// openController.
|
|
67433
|
+
// openController.openedInDom every time, not a frozen mount-time constant —
|
|
66743
67434
|
// see popover.jsx's own identical prop for the full reasoning) so a
|
|
66744
67435
|
// consumer whose own CSS also sets display (e.g. Popup's flex prop)
|
|
66745
67436
|
// can't silently defeat showModal()/close()'s native open/close — see
|
|
@@ -67115,7 +67806,7 @@ const css$D = /* css */`
|
|
|
67115
67806
|
without this it would flash visible for one frame on mount, before
|
|
67116
67807
|
openEffect's own JS ever gets a chance to hide it. [navi-hidden] is
|
|
67117
67808
|
set from usePopoverProps' own contentProps (recomputed from
|
|
67118
|
-
openController.
|
|
67809
|
+
openController.openedInDom on every render, present from the very first
|
|
67119
67810
|
one — see there for why), then toggled by plain
|
|
67120
67811
|
removeAttribute/setAttribute in openEffect/close, never an explicit
|
|
67121
67812
|
display override: removing the attribute just lets this rule stop
|
|
@@ -68471,7 +69162,7 @@ const usePopoverProps = props => {
|
|
|
68471
69162
|
"navi-out-of-flow": "",
|
|
68472
69163
|
"baseClassName": "navi_popover_backdrop",
|
|
68473
69164
|
"aria-hidden": "true",
|
|
68474
|
-
// Recomputed fresh on every render from openController.
|
|
69165
|
+
// Recomputed fresh on every render from openController.openedInDom (not a
|
|
68475
69166
|
// frozen mount-time constant, same pattern as navi-hidden just below)
|
|
68476
69167
|
// rather than driven through a mount-time layout effect — its own
|
|
68477
69168
|
// actual open/close toggling is still done entirely imperatively
|
|
@@ -68546,7 +69237,7 @@ const usePopoverProps = props => {
|
|
|
68546
69237
|
// starting-hidden default) — present from this very first render so
|
|
68547
69238
|
// there's no gap for the browser to ever paint it visible before
|
|
68548
69239
|
// anything has actually opened it. Recomputed fresh on every render from
|
|
68549
|
-
// openController.
|
|
69240
|
+
// openController.openedInDom (not a frozen mount-time constant) — Preact
|
|
68550
69241
|
// only touches the DOM for a prop whose value actually changed since
|
|
68551
69242
|
// the last render, so as long as this always reflects the *current*
|
|
68552
69243
|
// truth, it never fights the imperative
|
|
@@ -72972,7 +73663,6 @@ const ListUI = props => {
|
|
|
72972
73663
|
// What the runs ask for and stand for, in items: a source paginates in items,
|
|
72973
73664
|
// and asks for the first picture's items and the rest in one round trip.
|
|
72974
73665
|
listItems.pageSize = PAGE_SIZE_DEFAULT;
|
|
72975
|
-
listItems.scrolled = scrolled ?? defaultScrolled;
|
|
72976
73666
|
const {
|
|
72977
73667
|
virtualItemSizeSignal,
|
|
72978
73668
|
renderWindow,
|
|
@@ -73312,6 +74002,45 @@ const useListScrollSync = ({
|
|
|
73312
74002
|
columns
|
|
73313
74003
|
}) => {
|
|
73314
74004
|
const debugScroll = useDebugScroll();
|
|
74005
|
+
// Where the list must be: what the caller holds it at (`scrolled`), or where
|
|
74006
|
+
// it opens and then lets go (`defaultScrolled`). A `scrolled` that changes is
|
|
74007
|
+
// the caller moving the list, so the hold is armed again — that is what makes
|
|
74008
|
+
// it controlled. A `defaultScrolled` that changes is where the list opens,
|
|
74009
|
+
// read again from something that answered again (a count fresher than the
|
|
74010
|
+
// one kept from the previous visit): followed for as long as nobody has
|
|
74011
|
+
// moved the list, and compared by value, since it is said at every render.
|
|
74012
|
+
// Nothing to hold it at (a position not saved yet) is not a position: the
|
|
74013
|
+
// list opens where it opens.
|
|
74014
|
+
const startPlaceRef = useRef(null);
|
|
74015
|
+
if (startPlaceRef.current === null) {
|
|
74016
|
+
startPlaceRef.current = {
|
|
74017
|
+
userTookOver: false,
|
|
74018
|
+
wanted: scrolled ?? defaultScrolled,
|
|
74019
|
+
scrolled,
|
|
74020
|
+
defaultScrolled,
|
|
74021
|
+
way: null,
|
|
74022
|
+
arrived: false
|
|
74023
|
+
};
|
|
74024
|
+
} else {
|
|
74025
|
+
const place = startPlaceRef.current;
|
|
74026
|
+
if (place.scrolled !== scrolled) {
|
|
74027
|
+
place.scrolled = scrolled;
|
|
74028
|
+
place.defaultScrolled = defaultScrolled;
|
|
74029
|
+
place.wanted = scrolled ?? defaultScrolled;
|
|
74030
|
+
place.userTookOver = false;
|
|
74031
|
+
} else if ((scrolled === undefined || scrolled === null) && !compareTwoJsValues(place.defaultScrolled, defaultScrolled)) {
|
|
74032
|
+
place.defaultScrolled = defaultScrolled;
|
|
74033
|
+
if (!place.userTookOver) {
|
|
74034
|
+
place.wanted = defaultScrolled;
|
|
74035
|
+
// Placed again from where it stands: the way to the previous place
|
|
74036
|
+
// says nothing about whose the next scrolls are.
|
|
74037
|
+
place.way = null;
|
|
74038
|
+
place.arrived = false;
|
|
74039
|
+
}
|
|
74040
|
+
}
|
|
74041
|
+
}
|
|
74042
|
+
const scrolledWanted = startPlaceRef.current.wanted;
|
|
74043
|
+
listItems.scrolled = scrolledWanted;
|
|
73315
74044
|
// The items drawn, [start, end) among the list's own. A ref as well as a
|
|
73316
74045
|
// state: the render moves it where the list is held (holdWindow) without a
|
|
73317
74046
|
// commit of its own.
|
|
@@ -73326,7 +74055,7 @@ const useListScrollSync = ({
|
|
|
73326
74055
|
const virtualItemSizeSignal = useVirtualItemSizeSignal(ref, virtualItemSize, horizontal, {
|
|
73327
74056
|
inLines: Boolean(columns),
|
|
73328
74057
|
windowLeavesItemsOut,
|
|
73329
|
-
scrolledWanted
|
|
74058
|
+
scrolledWanted,
|
|
73330
74059
|
// The fillers hold the room of items above the screen at this size: a
|
|
73331
74060
|
// size that changes moves what is on screen, like items landing above it.
|
|
73332
74061
|
beforeSizeChange: () => listItems.captureAnchor()
|
|
@@ -73657,25 +74386,7 @@ const useListScrollSync = ({
|
|
|
73657
74386
|
const newEnd = newStart + windowSize;
|
|
73658
74387
|
updateRenderWindow(newStart, newEnd);
|
|
73659
74388
|
};
|
|
73660
|
-
|
|
73661
|
-
// Where the list must be: what the caller holds it at (`scrolled`), or where
|
|
73662
|
-
// it opens and then lets go (`defaultScrolled`). A `scrolled` that changes is
|
|
73663
|
-
// the caller moving the list, so the hold is armed again — that is what makes
|
|
73664
|
-
// it controlled.
|
|
73665
|
-
// Nothing to hold it at (a position not saved yet) is not a position: the
|
|
73666
|
-
// list opens where it opens.
|
|
73667
|
-
const scrolledWanted = scrolled ?? defaultScrolled;
|
|
73668
74389
|
const scrolledFallback = scrolled === undefined || scrolled === null ? "start" : defaultScrolled ?? "start";
|
|
73669
|
-
const startPlaceRef = useRef({
|
|
73670
|
-
userTookOver: false,
|
|
73671
|
-
wanted: scrolledWanted,
|
|
73672
|
-
way: null,
|
|
73673
|
-
arrived: false
|
|
73674
|
-
});
|
|
73675
|
-
if (startPlaceRef.current.wanted !== scrolledWanted) {
|
|
73676
|
-
startPlaceRef.current.wanted = scrolledWanted;
|
|
73677
|
-
startPlaceRef.current.userTookOver = false;
|
|
73678
|
-
}
|
|
73679
74390
|
// Only one thing owns the scroll at a time: while the list is holding itself
|
|
73680
74391
|
// somewhere, the anchoring stays out of it (holding an item still is precisely
|
|
73681
74392
|
// not being at the end anymore once what is above it shrinks).
|
|
@@ -77395,6 +78106,17 @@ const useItemStore = ({
|
|
|
77395
78106
|
}
|
|
77396
78107
|
const pages = pagesRef.current;
|
|
77397
78108
|
const [, setPageVersion] = useState(0);
|
|
78109
|
+
// A list on screen is part of what the page reads: the composition it reads
|
|
78110
|
+
// is kept with the page for the next document (see keepPageOnScreen).
|
|
78111
|
+
useLayoutEffect(() => {
|
|
78112
|
+
if (inMemory || typeof itemsAction !== "function") {
|
|
78113
|
+
return undefined;
|
|
78114
|
+
}
|
|
78115
|
+
if (!itemsAction.isRangeReader) {
|
|
78116
|
+
return undefined;
|
|
78117
|
+
}
|
|
78118
|
+
return showRangeReader(itemsAction);
|
|
78119
|
+
}, [inMemory, itemsAction]);
|
|
77398
78120
|
// The ranks whose item is from before: the run came back to the screen, or
|
|
77399
78121
|
// the source said the collection moved. They stay drawn until a page confirms
|
|
77400
78122
|
// or replaces them, rank by rank. A page covers what the window framed when it
|
|
@@ -77528,6 +78250,7 @@ const useItemStore = ({
|
|
|
77528
78250
|
}
|
|
77529
78251
|
}
|
|
77530
78252
|
if (typeof itemsAction === "function" && itemsAction.trimComposition) {
|
|
78253
|
+
itemsAction.frameComposition(windowFrom, windowTo);
|
|
77531
78254
|
itemsAction.trimComposition(keepFrom, keepTo, budget);
|
|
77532
78255
|
}
|
|
77533
78256
|
},
|
|
@@ -78097,6 +78820,9 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
78097
78820
|
* room has to be restated as a number by whoever asks. The `index` and
|
|
78098
78821
|
* `visibleCount` it also hands out say where to aim before the item is found,
|
|
78099
78822
|
* and how many items to draw before the first paint (see `renderBudget`).
|
|
78823
|
+
* A value that changes before anyone has moved the list is followed (a place
|
|
78824
|
+
* read from a count that answered again): it is compared by value, so it can
|
|
78825
|
+
* be computed at every render.
|
|
78100
78826
|
* @param {"start"|"end"|number|{id: string, index?: number, offset?: number, visibleCount?: number}} [props.scrolled]
|
|
78101
78827
|
* The same, but held: the list goes back there every time this changes, even
|
|
78102
78828
|
* after the user has scrolled — the caller owns where the list is (see
|
|
@@ -80965,6 +81691,8 @@ const TRAVELS_NONE = [];
|
|
|
80965
81691
|
* name?: string,
|
|
80966
81692
|
* min?: any,
|
|
80967
81693
|
* max?: any,
|
|
81694
|
+
* minMessage?: any,
|
|
81695
|
+
* maxMessage?: any,
|
|
80968
81696
|
* step?: number,
|
|
80969
81697
|
* type?: string,
|
|
80970
81698
|
* editable?: boolean,
|
|
@@ -81003,7 +81731,15 @@ const TRAVELS_NONE = [];
|
|
|
81003
81731
|
* @param {number} [step=1] How many steps a press covers.
|
|
81004
81732
|
* @param {number} [duration=250] How long a travel takes, in milliseconds.
|
|
81005
81733
|
* @param {any} [min] The first value one can reach; `max` is the last. Beyond
|
|
81006
|
-
* them the travel simply does not happen and the chevron that way says so
|
|
81734
|
+
* them the travel simply does not happen and the chevron that way says so,
|
|
81735
|
+
* in the words the control in the middle uses for a value past that end ("La
|
|
81736
|
+
* date doit être au plus tard le 2 avril 2027", "Max 5"). A value the control
|
|
81737
|
+
* has no such sentence for — what a plain `Spin` steps through — is "nothing
|
|
81738
|
+
* after this one".
|
|
81739
|
+
* @param {any} [maxMessage] What is said at `max` instead: by the chevron
|
|
81740
|
+
* refused there, by a travel asked past it, and by the control in the middle
|
|
81741
|
+
* for a value past it (picked in the calendar, typed). `minMessage` is its
|
|
81742
|
+
* twin at `min`.
|
|
81007
81743
|
* @param {string} [padding] The room around the value, `paddingX`/`paddingY`
|
|
81008
81744
|
* and the four sides included. It goes on what is inside the box rather than
|
|
81009
81745
|
* on the box: above and below it is taken by the value AND by the two
|
|
@@ -81033,6 +81769,8 @@ const Spin = ({
|
|
|
81033
81769
|
name,
|
|
81034
81770
|
min,
|
|
81035
81771
|
max,
|
|
81772
|
+
minMessage,
|
|
81773
|
+
maxMessage,
|
|
81036
81774
|
step = 1,
|
|
81037
81775
|
duration = 250,
|
|
81038
81776
|
type = "text",
|
|
@@ -81158,15 +81896,31 @@ const Spin = ({
|
|
|
81158
81896
|
const readOnlyResolved = Boolean(readOnly || readOnlyFromAbove || loadingResolved);
|
|
81159
81897
|
const disabledResolved = Boolean(disabled || disabledFromAbove);
|
|
81160
81898
|
|
|
81899
|
+
// The end of what one may reach, said the way the control in the middle
|
|
81900
|
+
// says it about a value past that end — its `minMessage`/`maxMessage`, or the
|
|
81901
|
+
// sentence its type has for the bound — so a chevron, the calendar and a
|
|
81902
|
+
// typed value refuse in the same words. Asked of the control at the press:
|
|
81903
|
+
// it is not there yet on the first render. `goingNext` is towards the bigger
|
|
81904
|
+
// values, whichever end of the box that is.
|
|
81905
|
+
const boundMessage = goingNext => {
|
|
81906
|
+
const constraint = goingNext ? MAX_CONSTRAINT : MIN_CONSTRAINT;
|
|
81907
|
+
const controlEl = controlRef.current;
|
|
81908
|
+
const controller = (findControlHost(controlEl) || controlEl).__uiStateController__;
|
|
81909
|
+
const {
|
|
81910
|
+
message
|
|
81911
|
+
} = getConstraintMessage(controller, constraint, constraint.messageAtBound(controller), {});
|
|
81912
|
+
return message ?? naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before");
|
|
81913
|
+
};
|
|
81914
|
+
|
|
81161
81915
|
// Why a chevron refuses, in its own words — and only when the reason is
|
|
81162
81916
|
// ours: the end of what one may reach is something this control knows and
|
|
81163
81917
|
// navi cannot guess. For everything else the reason belongs to the state the
|
|
81164
81918
|
// whole control is in, and it is said about the control ("read-only",
|
|
81165
81919
|
// "busy") rather than about the button, which is not what the user was
|
|
81166
81920
|
// pressing.
|
|
81167
|
-
const wayOutMessage = (allowed,
|
|
81921
|
+
const wayOutMessage = (allowed, isNext) => {
|
|
81168
81922
|
if (!allowed) {
|
|
81169
|
-
return
|
|
81923
|
+
return boundMessage(isNext);
|
|
81170
81924
|
}
|
|
81171
81925
|
if (loadingResolved) {
|
|
81172
81926
|
return naviI18n("constraint.busy.default");
|
|
@@ -81278,7 +82032,7 @@ const Spin = ({
|
|
|
81278
82032
|
// answer belongs.
|
|
81279
82033
|
e.preventDefault();
|
|
81280
82034
|
e.stopPropagation();
|
|
81281
|
-
openCallout(
|
|
82035
|
+
openCallout(boundMessage(goingNext), {
|
|
81282
82036
|
anchorElement: middleRef.current,
|
|
81283
82037
|
status: "info",
|
|
81284
82038
|
openingEvent: e.detail.event ?? e
|
|
@@ -81337,12 +82091,14 @@ const Spin = ({
|
|
|
81337
82091
|
const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
|
|
81338
82092
|
const wayOut = atStart => {
|
|
81339
82093
|
const isNext = atStart ? startIsNext : !startIsNext;
|
|
82094
|
+
const allowed = atStart ? startAllowed : endAllowed;
|
|
81340
82095
|
return jsx(WayOut, {
|
|
81341
82096
|
atStart: atStart,
|
|
81342
82097
|
onPointerDown: e => {
|
|
81343
82098
|
wayOutPointerTypeRef.current = e.pointerType;
|
|
81344
82099
|
},
|
|
81345
|
-
|
|
82100
|
+
unavailable: !allowed || loadingResolved || readOnlyResolved,
|
|
82101
|
+
unavailableMessage: () => wayOutMessage(allowed, isNext),
|
|
81346
82102
|
label: isNext ? nextLabel ?? naviI18n("spin.next") : previousLabel ?? naviI18n("spin.previous"),
|
|
81347
82103
|
onPress: e => {
|
|
81348
82104
|
focusMiddle();
|
|
@@ -81389,6 +82145,8 @@ const Spin = ({
|
|
|
81389
82145
|
...valueProps,
|
|
81390
82146
|
min: min,
|
|
81391
82147
|
max: max,
|
|
82148
|
+
minMessage: minMessage,
|
|
82149
|
+
maxMessage: maxMessage,
|
|
81392
82150
|
step: step,
|
|
81393
82151
|
readOnly: readOnly,
|
|
81394
82152
|
disabled: disabled,
|
|
@@ -81422,6 +82180,8 @@ const Spin = ({
|
|
|
81422
82180
|
...valueProps,
|
|
81423
82181
|
min: min,
|
|
81424
82182
|
max: max,
|
|
82183
|
+
minMessage: minMessage,
|
|
82184
|
+
maxMessage: maxMessage,
|
|
81425
82185
|
readOnly: readOnly,
|
|
81426
82186
|
disabled: disabled,
|
|
81427
82187
|
loading: loading,
|
|
@@ -81532,6 +82292,7 @@ const Spin = ({
|
|
|
81532
82292
|
const WayOut = ({
|
|
81533
82293
|
atStart,
|
|
81534
82294
|
commandFor,
|
|
82295
|
+
unavailable,
|
|
81535
82296
|
unavailableMessage,
|
|
81536
82297
|
label,
|
|
81537
82298
|
onPress,
|
|
@@ -81542,10 +82303,10 @@ const WayOut = ({
|
|
|
81542
82303
|
// onPointerUp below).
|
|
81543
82304
|
const pointerDownRef = useRef(null);
|
|
81544
82305
|
const press = e => {
|
|
81545
|
-
if (
|
|
82306
|
+
if (unavailable) {
|
|
81546
82307
|
// Why it does nothing, said where one pressed: a control would have
|
|
81547
82308
|
// done this through its own interaction gate, and this one has none.
|
|
81548
|
-
openCallout(unavailableMessage, {
|
|
82309
|
+
openCallout(unavailableMessage(), {
|
|
81549
82310
|
anchorElement: e.currentTarget,
|
|
81550
82311
|
status: "info",
|
|
81551
82312
|
openingEvent: e
|
|
@@ -81572,8 +82333,8 @@ const WayOut = ({
|
|
|
81572
82333
|
,
|
|
81573
82334
|
role: "button",
|
|
81574
82335
|
"aria-label": label,
|
|
81575
|
-
"aria-disabled":
|
|
81576
|
-
"data-unavailable":
|
|
82336
|
+
"aria-disabled": unavailable ? "true" : undefined,
|
|
82337
|
+
"data-unavailable": unavailable ? "" : undefined
|
|
81577
82338
|
// At the chevron, not beside it: a callout aims its arrow at where the
|
|
81578
82339
|
// anchor's text starts, and there is no text here — only a glyph in the
|
|
81579
82340
|
// middle of the box, which is what one pressed and what the answer is
|
|
@@ -81806,6 +82567,8 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
81806
82567
|
* defaultValue?: number|string,
|
|
81807
82568
|
* min?: number,
|
|
81808
82569
|
* max?: number,
|
|
82570
|
+
* minMessage?: any,
|
|
82571
|
+
* maxMessage?: any,
|
|
81809
82572
|
* step?: number,
|
|
81810
82573
|
* pad?: number,
|
|
81811
82574
|
* loop?: boolean,
|
|
@@ -81911,6 +82674,8 @@ const numberAtStep = (value, count, {
|
|
|
81911
82674
|
* name?: string,
|
|
81912
82675
|
* min?: string,
|
|
81913
82676
|
* max?: string,
|
|
82677
|
+
* minMessage?: any,
|
|
82678
|
+
* maxMessage?: any,
|
|
81914
82679
|
* step?: number,
|
|
81915
82680
|
* lang?: string,
|
|
81916
82681
|
* renderDay?: (day: string) => import("ignore:preact").ComponentChildren,
|
|
@@ -93478,5 +94243,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
93478
94243
|
})
|
|
93479
94244
|
});
|
|
93480
94245
|
|
|
93481
|
-
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 };
|
|
94246
|
+
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 };
|
|
93482
94247
|
//# sourceMappingURL=jsenv_navi.js.map
|