@jsenv/navi 0.29.419 → 0.29.420
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 +392 -153
- package/dist/dev/jsenv_navi.js.map +30 -30
- package/dist/jsenv_navi.js +373 -146
- package/dist/jsenv_navi.js.map +30 -30
- package/docs/create_and_edit.md +10 -0
- package/docs/data_states.md +6 -7
- package/docs/form_changed.md +5 -3
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -399,6 +399,22 @@ const whenRenderingResumes = (callback) => {
|
|
|
399
399
|
renderingHold.waiting.push(callback);
|
|
400
400
|
};
|
|
401
401
|
|
|
402
|
+
// After the render the change being applied has queued, for what depends on
|
|
403
|
+
// the page that change puts on screen — an offset given back to a page is
|
|
404
|
+
// clamped to whatever page the document holds when it is written. Under a hold
|
|
405
|
+
// that render runs when the hold is released, and this right after it.
|
|
406
|
+
// Otherwise the render is Preact's own, queued in a microtask, and every
|
|
407
|
+
// microtask has run by the next frame — before it paints.
|
|
408
|
+
const whenPageRendered = (callback) => {
|
|
409
|
+
if (renderingHold) {
|
|
410
|
+
renderingHold.waiting.push(callback);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
requestAnimationFrame(() => {
|
|
414
|
+
callback();
|
|
415
|
+
});
|
|
416
|
+
};
|
|
417
|
+
|
|
402
418
|
// The hold a navigation takes on its way in — from before its first write,
|
|
403
419
|
// because by the time a route announces that it matches, Preact has already
|
|
404
420
|
// been told and the render is queued; a hold taken then is a hold taken too
|
|
@@ -773,7 +789,10 @@ const writeTarget = (element) => {
|
|
|
773
789
|
silencedElement.id = addressTargetId;
|
|
774
790
|
}
|
|
775
791
|
window.history.replaceState(historyState, null, address);
|
|
776
|
-
|
|
792
|
+
// Only a state the entry had is put back: an entry nobody wrote one on
|
|
793
|
+
// (via_history.js never does) has nothing to lose, and updateCurrentEntry
|
|
794
|
+
// cannot say "none" — `state` is a required member, and undefined throws.
|
|
795
|
+
if (entry && navigation.currentEntry && entryState !== undefined) {
|
|
777
796
|
navigation.updateCurrentEntry({ state: entryState });
|
|
778
797
|
}
|
|
779
798
|
targetWriteInProgress = false;
|
|
@@ -3527,6 +3546,8 @@ const actionWeakMap = new WeakMap();
|
|
|
3527
3546
|
* than a first load (data_states.md). Called at the start of every run whose
|
|
3528
3547
|
* value is undefined; the answer replaces it as usual, and `undefined` means
|
|
3529
3548
|
* "nothing known". A resource `GET` uses it to draw the row its store holds.
|
|
3549
|
+
* An action declaring it is asked for it by a binding moving to new params,
|
|
3550
|
+
* rather than handed the previous params' answer (see inheritData).
|
|
3530
3551
|
* @param {{ ms?: number, max?: number }} [rootOptions.keep] - how long an
|
|
3531
3552
|
* answer stays good once it has landed. Without it an answer lives exactly as
|
|
3532
3553
|
* long as something references it: the screen that asked goes away, and
|
|
@@ -4184,7 +4205,14 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4184
4205
|
if (rejected) {
|
|
4185
4206
|
return failRun(rejectedValue);
|
|
4186
4207
|
}
|
|
4187
|
-
|
|
4208
|
+
// Turning the answer into a value can throw as well — a resource
|
|
4209
|
+
// write answering nothing to upsert. That fails the run, as the
|
|
4210
|
+
// catch below does when the callback answered synchronously.
|
|
4211
|
+
try {
|
|
4212
|
+
return onRunEnd();
|
|
4213
|
+
} catch (e) {
|
|
4214
|
+
return failRun(e);
|
|
4215
|
+
}
|
|
4188
4216
|
});
|
|
4189
4217
|
} catch (e) {
|
|
4190
4218
|
return failRun(e);
|
|
@@ -4224,6 +4252,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4224
4252
|
|
|
4225
4253
|
const privateProperties = {
|
|
4226
4254
|
valueInitial,
|
|
4255
|
+
provisionalValue,
|
|
4227
4256
|
|
|
4228
4257
|
performRun,
|
|
4229
4258
|
performReset,
|
|
@@ -4255,7 +4284,8 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4255
4284
|
* This enables live updates - for example, performing an HTTP GET request every time
|
|
4256
4285
|
* a list of filters changes, providing real-time results without user interaction.
|
|
4257
4286
|
* @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
|
|
4258
|
-
* By default (true), the proxy carries over the previous target's value and error into the new action
|
|
4287
|
+
* By default (true), the proxy carries over the previous target's value and error into the new action —
|
|
4288
|
+
* unless the action declares a `provisionalValue`, which then says what the new one holds.
|
|
4259
4289
|
* This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
|
|
4260
4290
|
* new action completes, not when it starts loading. Code that needs to distinguish loading state can still
|
|
4261
4291
|
* check `action.runningState`, while code that just reads `action.data` always sees the most recent
|
|
@@ -4302,8 +4332,14 @@ const createActionProxyFromSignal = (
|
|
|
4302
4332
|
let currentActionPrivateProperties = getActionPrivateProperties(action);
|
|
4303
4333
|
let actionTargetPreviousWeakRef = null;
|
|
4304
4334
|
|
|
4335
|
+
// An action saying for itself what it holds for params it has not answered
|
|
4336
|
+
// (provisionalValue: a resource GET draws the row its store holds) is not
|
|
4337
|
+
// handed the previous params' answer — an answer about another row. What it
|
|
4338
|
+
// does not know stays unknown: a skeleton, never a different row.
|
|
4339
|
+
const inheritsAnswer =
|
|
4340
|
+
inheritData && !getActionPrivateProperties(action).provisionalValue;
|
|
4305
4341
|
const createTarget = (params) => {
|
|
4306
|
-
if (
|
|
4342
|
+
if (inheritsAnswer) {
|
|
4307
4343
|
const previousActionTarget = actionTargetPreviousWeakRef?.deref();
|
|
4308
4344
|
const previousTarget = previousActionTarget || action;
|
|
4309
4345
|
return action.bindParams(params, {
|
|
@@ -4416,6 +4452,12 @@ const createActionProxyFromSignal = (
|
|
|
4416
4452
|
return signalProxy;
|
|
4417
4453
|
};
|
|
4418
4454
|
|
|
4455
|
+
// Params bound onto this binding make the action bound to both — these over
|
|
4456
|
+
// the ones it follows, as an instance's go over its action's — following the
|
|
4457
|
+
// signals of both: what a control does with the action it is given, and
|
|
4458
|
+
// actionRunEffect. Kept per params like the action's own bindings, since a
|
|
4459
|
+
// control binds its action again at every render.
|
|
4460
|
+
const bindingOnProxyWeakMap = createJsValueWeakMap();
|
|
4419
4461
|
Object.assign(actionProxy, {
|
|
4420
4462
|
isAction: true,
|
|
4421
4463
|
isProxy: true,
|
|
@@ -4440,14 +4482,27 @@ const createActionProxyFromSignal = (
|
|
|
4440
4482
|
});
|
|
4441
4483
|
return currentAction;
|
|
4442
4484
|
},
|
|
4443
|
-
bindParams: () => {
|
|
4444
|
-
|
|
4445
|
-
|
|
4485
|
+
bindParams: (newParamsOrSignal, options) => {
|
|
4486
|
+
const existingBinding = bindingOnProxyWeakMap.get(newParamsOrSignal);
|
|
4487
|
+
if (existingBinding) {
|
|
4488
|
+
return existingBinding;
|
|
4489
|
+
}
|
|
4490
|
+
const binding = action.bindParams(
|
|
4491
|
+
computed(() =>
|
|
4492
|
+
mergeActionParams(
|
|
4493
|
+
paramsSignal.value,
|
|
4494
|
+
readParamsHandedToBind(newParamsOrSignal),
|
|
4495
|
+
),
|
|
4496
|
+
),
|
|
4497
|
+
options,
|
|
4446
4498
|
);
|
|
4499
|
+
bindingOnProxyWeakMap.set(newParamsOrSignal, binding);
|
|
4500
|
+
return binding;
|
|
4447
4501
|
},
|
|
4448
4502
|
replaceParams: null, // Will be set below
|
|
4449
4503
|
toString: () => callSourceSignal.peek(), // peek: see the action's toString
|
|
4450
4504
|
meta: action.meta,
|
|
4505
|
+
debug: action.debug,
|
|
4451
4506
|
|
|
4452
4507
|
paramsSignal: proxyParamsSignal,
|
|
4453
4508
|
paramsSettlingSignal,
|
|
@@ -4590,6 +4645,29 @@ const generateActionCallSource = (name, params) => {
|
|
|
4590
4645
|
return `${name}${argsString}`;
|
|
4591
4646
|
};
|
|
4592
4647
|
|
|
4648
|
+
// What params handed to bindParams say at this moment, read as a binding reads
|
|
4649
|
+
// them (see _bindParams): a signal is its value, and so is each signal a plain
|
|
4650
|
+
// object holds at its first level. Read inside a computed, it follows them.
|
|
4651
|
+
const readParamsHandedToBind = (newParamsOrSignal) => {
|
|
4652
|
+
if (isSignal(newParamsOrSignal)) {
|
|
4653
|
+
return newParamsOrSignal.value;
|
|
4654
|
+
}
|
|
4655
|
+
if (!isPlainObject(newParamsOrSignal)) {
|
|
4656
|
+
return newParamsOrSignal;
|
|
4657
|
+
}
|
|
4658
|
+
const params = {};
|
|
4659
|
+
for (const key of Object.keys(newParamsOrSignal)) {
|
|
4660
|
+
const value = newParamsOrSignal[key];
|
|
4661
|
+
const valueSignal = isSignal(value)
|
|
4662
|
+
? value
|
|
4663
|
+
: value
|
|
4664
|
+
? value[SYMBOL_OBJECT_SIGNAL]
|
|
4665
|
+
: undefined;
|
|
4666
|
+
params[key] = valueSignal ? valueSignal.value : value;
|
|
4667
|
+
}
|
|
4668
|
+
return params;
|
|
4669
|
+
};
|
|
4670
|
+
|
|
4593
4671
|
const isPlainObject = (obj) => {
|
|
4594
4672
|
if (typeof obj !== "object" || obj === null) {
|
|
4595
4673
|
return false;
|
|
@@ -4702,9 +4780,16 @@ const actionRunEffect = (
|
|
|
4702
4780
|
}
|
|
4703
4781
|
return params;
|
|
4704
4782
|
});
|
|
4783
|
+
// Whether the getter asks for a run is read off what it returned, never off
|
|
4784
|
+
// the params of the instance it leads to: an action handed here holding
|
|
4785
|
+
// params of its own (an instance, a binding) keeps them when the getter
|
|
4786
|
+
// returns nothing — and nothing must still run nothing.
|
|
4787
|
+
let asking = Boolean(actionParamsSignal.peek());
|
|
4705
4788
|
const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
|
|
4706
4789
|
debounce,
|
|
4707
4790
|
onChange: (actionTarget, actionTargetPrevious, { explicitRunIntent }) => {
|
|
4791
|
+
const askingBefore = asking;
|
|
4792
|
+
asking = Boolean(actionParamsSignal.peek());
|
|
4708
4793
|
if (explicitRunIntent) {
|
|
4709
4794
|
// The caller already issued an explicit run/rerun/prerun/reset/abort —
|
|
4710
4795
|
// don't attempt to also auto-run from the params change to avoid double-runs.
|
|
@@ -4715,7 +4800,7 @@ const actionRunEffect = (
|
|
|
4715
4800
|
}
|
|
4716
4801
|
if (!actionTargetPrevious && actionTarget) {
|
|
4717
4802
|
// first run
|
|
4718
|
-
if (!
|
|
4803
|
+
if (!asking) {
|
|
4719
4804
|
// falsy params, don't run
|
|
4720
4805
|
return;
|
|
4721
4806
|
}
|
|
@@ -4731,12 +4816,12 @@ const actionRunEffect = (
|
|
|
4731
4816
|
actionTarget
|
|
4732
4817
|
) {
|
|
4733
4818
|
// params changed
|
|
4734
|
-
if (!
|
|
4819
|
+
if (!asking) {
|
|
4735
4820
|
// falsy params, don't run
|
|
4736
4821
|
actionTargetPrevious.abort("abortOnFalsyParams");
|
|
4737
4822
|
return;
|
|
4738
4823
|
}
|
|
4739
|
-
if (!
|
|
4824
|
+
if (!askingBefore) {
|
|
4740
4825
|
// coming from falsy-params state: action may already be cached, avoid unnecessary rerun
|
|
4741
4826
|
runUnwatched(() =>
|
|
4742
4827
|
actionTarget.run({ reason: "params restored from falsy state" }),
|
|
@@ -4748,7 +4833,7 @@ const actionRunEffect = (
|
|
|
4748
4833
|
},
|
|
4749
4834
|
...options,
|
|
4750
4835
|
});
|
|
4751
|
-
if (
|
|
4836
|
+
if (asking) {
|
|
4752
4837
|
runUnwatched(() =>
|
|
4753
4838
|
actionRunnedByThisEffect.run({ reason: "initial truthy params" }),
|
|
4754
4839
|
);
|
|
@@ -6624,6 +6709,28 @@ let navDepthMax = 0;
|
|
|
6624
6709
|
|
|
6625
6710
|
const getNavDepth = () => navDepth;
|
|
6626
6711
|
|
|
6712
|
+
// Where an entry's state says it stands, undefined when it says nothing.
|
|
6713
|
+
const readNavDepthInState = (state) => {
|
|
6714
|
+
if (state && typeof state[NAV_DEPTH_STATE_KEY] === "number") {
|
|
6715
|
+
return state[NAV_DEPTH_STATE_KEY];
|
|
6716
|
+
}
|
|
6717
|
+
return undefined;
|
|
6718
|
+
};
|
|
6719
|
+
|
|
6720
|
+
// The state of the entry a document is loaded into, carrying its depth. One
|
|
6721
|
+
// that says a depth was written by this document (a reload in the middle of
|
|
6722
|
+
// the stack) and keeps it. One that says none was not: a url opened cold, the
|
|
6723
|
+
// first of the stack — or, rarely, an entry the browser made for a fragment,
|
|
6724
|
+
// reloaded, which 0 under-reports as above. Written into the entry by the
|
|
6725
|
+
// integrations, because a back that returns there reads where it stands from
|
|
6726
|
+
// the entry alone.
|
|
6727
|
+
const stateWithNavDepthOnLoad = (state) => {
|
|
6728
|
+
if (readNavDepthInState(state) !== undefined) {
|
|
6729
|
+
return state;
|
|
6730
|
+
}
|
|
6731
|
+
return { ...state, [NAV_DEPTH_STATE_KEY]: 0 };
|
|
6732
|
+
};
|
|
6733
|
+
|
|
6627
6734
|
const applyNavigationToNavDepth = (navigationType, state) => {
|
|
6628
6735
|
if (navigationType === "push") {
|
|
6629
6736
|
navDepth++;
|
|
@@ -6631,10 +6738,7 @@ const applyNavigationToNavDepth = (navigationType, state) => {
|
|
|
6631
6738
|
navDepthMax = navDepth;
|
|
6632
6739
|
} else if (navigationType === "replace") ; else {
|
|
6633
6740
|
// load, reload, traverse: the entry itself says where it stands.
|
|
6634
|
-
const depthInState =
|
|
6635
|
-
state && typeof state[NAV_DEPTH_STATE_KEY] === "number"
|
|
6636
|
-
? state[NAV_DEPTH_STATE_KEY]
|
|
6637
|
-
: undefined;
|
|
6741
|
+
const depthInState = readNavDepthInState(state);
|
|
6638
6742
|
if (depthInState !== undefined) {
|
|
6639
6743
|
navDepth = depthInState;
|
|
6640
6744
|
if (navDepth > navDepthMax) {
|
|
@@ -10115,6 +10219,18 @@ const preloadUrl = (url) => {
|
|
|
10115
10219
|
}
|
|
10116
10220
|
};
|
|
10117
10221
|
|
|
10222
|
+
// What a reload asks of each action bound to a route (see routeAction): the
|
|
10223
|
+
// action to run again, or nothing when its params ask nothing.
|
|
10224
|
+
const routeReloadMap = new WeakMap();
|
|
10225
|
+
const registerRouteReload = (route, reload) => {
|
|
10226
|
+
let reloadSet = routeReloadMap.get(route);
|
|
10227
|
+
if (!reloadSet) {
|
|
10228
|
+
reloadSet = new Set();
|
|
10229
|
+
routeReloadMap.set(route, reloadSet);
|
|
10230
|
+
}
|
|
10231
|
+
reloadSet.add(reload);
|
|
10232
|
+
};
|
|
10233
|
+
|
|
10118
10234
|
const routeStatePreloadMap = new WeakMap();
|
|
10119
10235
|
const registerRouteStatePreload = (route, preload) => {
|
|
10120
10236
|
let preloadSet = routeStatePreloadMap.get(route);
|
|
@@ -10204,6 +10320,7 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
10204
10320
|
const updateRoutes = (
|
|
10205
10321
|
url,
|
|
10206
10322
|
{
|
|
10323
|
+
navigationType,
|
|
10207
10324
|
isVisited = () => false,
|
|
10208
10325
|
// state
|
|
10209
10326
|
} = {},
|
|
@@ -10503,6 +10620,27 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
10503
10620
|
}
|
|
10504
10621
|
}
|
|
10505
10622
|
|
|
10623
|
+
// A route's actions run again when their params change (see routeAction),
|
|
10624
|
+
// and a reload changes none: it asks again for the page one is on. So the
|
|
10625
|
+
// actions of every matching route are handed back, to be rerun — except
|
|
10626
|
+
// those whose params ask nothing.
|
|
10627
|
+
if (navigationType === "reload") {
|
|
10628
|
+
const reloadSet = new Set();
|
|
10629
|
+
for (const route of returnValue.matchingRouteSet) {
|
|
10630
|
+
const routeReloadSet = routeReloadMap.get(route);
|
|
10631
|
+
if (!routeReloadSet) {
|
|
10632
|
+
continue;
|
|
10633
|
+
}
|
|
10634
|
+
for (const reload of routeReloadSet) {
|
|
10635
|
+
const actionToRerun = reload();
|
|
10636
|
+
if (actionToRerun) {
|
|
10637
|
+
reloadSet.add(actionToRerun);
|
|
10638
|
+
}
|
|
10639
|
+
}
|
|
10640
|
+
}
|
|
10641
|
+
returnValue.reloadSet = reloadSet;
|
|
10642
|
+
}
|
|
10643
|
+
|
|
10506
10644
|
return returnValue;
|
|
10507
10645
|
};
|
|
10508
10646
|
|
|
@@ -10890,11 +11028,18 @@ const installScrollRestoration = () => {
|
|
|
10890
11028
|
}
|
|
10891
11029
|
};
|
|
10892
11030
|
|
|
11031
|
+
// Where the document lands is the last arrival's to say. A return waits for its
|
|
11032
|
+
// page (see restoreScrollPositionOnReturn), and an arrival decided meanwhile — a
|
|
11033
|
+
// page that sends the reader elsewhere as it mounts — must not be scrolled to
|
|
11034
|
+
// where the return was going.
|
|
11035
|
+
let arrivalCount = 0;
|
|
11036
|
+
|
|
10893
11037
|
// Nothing to put back is not the same as putting back the top: a page arrived
|
|
10894
11038
|
// at for the first time is startAtTop's business, and this must not step on it.
|
|
10895
11039
|
// Whether there was anything, for a caller who has an answer of its own for the
|
|
10896
11040
|
// page that has never been read.
|
|
10897
11041
|
const restoreScrollPosition = (url) => {
|
|
11042
|
+
arrivalCount++;
|
|
10898
11043
|
const position = positionByUrl.get(new URL(url, window.location.href).href);
|
|
10899
11044
|
if (!position) {
|
|
10900
11045
|
return false;
|
|
@@ -10908,6 +11053,28 @@ const restoreScrollPosition = (url) => {
|
|
|
10908
11053
|
return true;
|
|
10909
11054
|
};
|
|
10910
11055
|
|
|
11056
|
+
// A traversal back to `url`. The offset is written once the page returned to is
|
|
11057
|
+
// rendered: written sooner, the document still holds the page being left and
|
|
11058
|
+
// clamps it to that page's height. It is read at once all the same — a page
|
|
11059
|
+
// left taller than the one arriving is clamped by the render itself, and that
|
|
11060
|
+
// clamp is a scroll recorded under the url already returned to.
|
|
11061
|
+
const restoreScrollPositionOnReturn = (url) => {
|
|
11062
|
+
const arrival = ++arrivalCount;
|
|
11063
|
+
const position = positionByUrl.get(new URL(url, window.location.href).href);
|
|
11064
|
+
if (!position) {
|
|
11065
|
+
return;
|
|
11066
|
+
}
|
|
11067
|
+
whenPageRendered(() => {
|
|
11068
|
+
if (arrival !== arrivalCount) {
|
|
11069
|
+
return;
|
|
11070
|
+
}
|
|
11071
|
+
if (documentHoldCount) {
|
|
11072
|
+
return;
|
|
11073
|
+
}
|
|
11074
|
+
scrollTo(position);
|
|
11075
|
+
});
|
|
11076
|
+
};
|
|
11077
|
+
|
|
10911
11078
|
// A page one arrives at for the first time starts at its top. Only a document
|
|
10912
11079
|
// navigation does that on its own: a pushState creates its entry with whatever
|
|
10913
11080
|
// scroll happened to be there, so without this the page opens at the offset of
|
|
@@ -10933,6 +11100,7 @@ const startAtTop = (url, { from } = {}) => {
|
|
|
10933
11100
|
if (!isArrival(url, { from })) {
|
|
10934
11101
|
return;
|
|
10935
11102
|
}
|
|
11103
|
+
arrivalCount++;
|
|
10936
11104
|
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
10937
11105
|
};
|
|
10938
11106
|
const isArrival = (url, { from }) => {
|
|
@@ -11403,12 +11571,12 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11403
11571
|
isVisited,
|
|
11404
11572
|
state,
|
|
11405
11573
|
});
|
|
11406
|
-
// Where the document lands, said by what kind of arrival this is.
|
|
11407
|
-
//
|
|
11408
|
-
//
|
|
11409
|
-
//
|
|
11410
|
-
//
|
|
11411
|
-
// it is
|
|
11574
|
+
// Where the document lands, said by what kind of arrival this is. Never
|
|
11575
|
+
// before the picture of a transition is taken, which would be of a page
|
|
11576
|
+
// at its top (see rendering_hold.js), and after the history has been
|
|
11577
|
+
// written, so the entry being left keeps the offset it is at. A return
|
|
11578
|
+
// also waits for the page it returns to: an offset is clamped to whatever
|
|
11579
|
+
// page the document holds when it is written.
|
|
11412
11580
|
//
|
|
11413
11581
|
// A replace gets neither: it is the same place said differently — a param
|
|
11414
11582
|
// settling, a state written — and moving the reader for it would throw
|
|
@@ -11419,7 +11587,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11419
11587
|
if (navigationType === "push") {
|
|
11420
11588
|
whenRenderingResumes(() => startAtTop(url, { from: urlLeft }));
|
|
11421
11589
|
} else if (navigationType === "traverse") {
|
|
11422
|
-
|
|
11590
|
+
restoreScrollPositionOnReturn(url);
|
|
11423
11591
|
}
|
|
11424
11592
|
executeWithCleanup(
|
|
11425
11593
|
() => allResult,
|
|
@@ -11654,11 +11822,12 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11654
11822
|
const init = () => {
|
|
11655
11823
|
const url = window.location.href;
|
|
11656
11824
|
const stateOnEntry = window.history.state;
|
|
11657
|
-
const state = dropGeneratedIdKeys(stateOnEntry);
|
|
11825
|
+
const state = stateWithNavDepthOnLoad(dropGeneratedIdKeys(stateOnEntry));
|
|
11658
11826
|
if (state !== stateOnEntry) {
|
|
11659
|
-
// The entry itself has to
|
|
11660
|
-
// getDocumentState() reads the entry back,
|
|
11661
|
-
//
|
|
11827
|
+
// The entry itself has to carry it, not just the document state:
|
|
11828
|
+
// getDocumentState() reads the entry back, every state write copies what
|
|
11829
|
+
// it finds there onto the next one, and a back returning here reads its
|
|
11830
|
+
// depth from it.
|
|
11662
11831
|
writeHistoryEntry("replace", state, url);
|
|
11663
11832
|
}
|
|
11664
11833
|
handleRoutingTask(url, {
|
|
@@ -11775,7 +11944,6 @@ const applyRouting = (
|
|
|
11775
11944
|
url,
|
|
11776
11945
|
{
|
|
11777
11946
|
globalAbortSignal,
|
|
11778
|
-
abortSignal,
|
|
11779
11947
|
// state
|
|
11780
11948
|
navigationType,
|
|
11781
11949
|
isVisited,
|
|
@@ -11787,45 +11955,36 @@ const applyRouting = (
|
|
|
11787
11955
|
// likely because code does not uses routing at all
|
|
11788
11956
|
return {};
|
|
11789
11957
|
}
|
|
11790
|
-
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
routeLoadRequestedMap,
|
|
11795
|
-
activeRouteSet,
|
|
11796
|
-
} = updateRoutes(url, {
|
|
11958
|
+
// The routes' actions follow the address by themselves (see routeAction):
|
|
11959
|
+
// what a navigation leaves to the routing is a reload, the one that asks
|
|
11960
|
+
// them again with the address unchanged.
|
|
11961
|
+
const { reloadSet } = updateRoutes(url, {
|
|
11797
11962
|
navigationType,
|
|
11798
11963
|
isVisited,
|
|
11799
11964
|
// state,
|
|
11800
11965
|
});
|
|
11801
|
-
if (
|
|
11802
|
-
(!loadSet || loadSet.size === 0) &&
|
|
11803
|
-
(!reloadSet || reloadSet.size === 0)
|
|
11804
|
-
) {
|
|
11966
|
+
if (!reloadSet || reloadSet.size === 0) {
|
|
11805
11967
|
return {
|
|
11806
11968
|
allResult: undefined,
|
|
11807
11969
|
requestedResult: undefined,
|
|
11808
|
-
activeRouteSet: new Set(),
|
|
11809
11970
|
};
|
|
11810
11971
|
}
|
|
11811
11972
|
const updateActionsResult = updateActions({
|
|
11812
11973
|
globalAbortSignal,
|
|
11813
|
-
|
|
11814
|
-
|
|
11974
|
+
// Not the navigation's own signal: the next navigation aborts that one, and
|
|
11975
|
+
// an action whose route still matches there would be left aborted — its
|
|
11976
|
+
// params do not change, so nothing would run it again.
|
|
11977
|
+
abortSignal: new AbortController().signal,
|
|
11815
11978
|
rerunSet: reloadSet,
|
|
11816
|
-
abortSignalMap,
|
|
11817
11979
|
reason,
|
|
11818
|
-
isReplace: navigationType === "replace",
|
|
11819
11980
|
});
|
|
11820
11981
|
const { allResult, runningActionSet } = updateActionsResult;
|
|
11821
11982
|
const pendingTaskNameArray = [];
|
|
11822
|
-
for (const
|
|
11823
|
-
|
|
11824
|
-
pendingTaskNameArray.push(`${route.relativeUrl} -> ${routeAction.name}`);
|
|
11825
|
-
}
|
|
11983
|
+
for (const runningAction of runningActionSet) {
|
|
11984
|
+
pendingTaskNameArray.push(runningAction.name);
|
|
11826
11985
|
}
|
|
11827
11986
|
routingWhile(() => allResult, pendingTaskNameArray);
|
|
11828
|
-
return
|
|
11987
|
+
return updateActionsResult;
|
|
11829
11988
|
};
|
|
11830
11989
|
|
|
11831
11990
|
const setupBrowserIntegration =
|
|
@@ -11907,6 +12066,12 @@ const stopLoad = (reason = "stopLoad() called") => {
|
|
|
11907
12066
|
browserIntegration.stop(reason);
|
|
11908
12067
|
}
|
|
11909
12068
|
};
|
|
12069
|
+
/**
|
|
12070
|
+
* Asks again for the page one is on, without reloading the document: the
|
|
12071
|
+
* actions of the routes matching the url run again (see routeAction), one that
|
|
12072
|
+
* failed included — what a "Retry" under a page that could not load calls.
|
|
12073
|
+
* The address, the history and the scroll stay as they are.
|
|
12074
|
+
*/
|
|
11910
12075
|
const reload = browserIntegration.reload;
|
|
11911
12076
|
/**
|
|
11912
12077
|
* Go back to the screen this document came from.
|
|
@@ -11929,11 +12094,13 @@ const reload = browserIntegration.reload;
|
|
|
11929
12094
|
* way a screen closed over a url keeps what was written to the url while it
|
|
11930
12095
|
* was open (see useNavState's leave()).
|
|
11931
12096
|
* @returns {Promise<boolean>|undefined}
|
|
11932
|
-
* When there is something to go back to: a promise resolved
|
|
11933
|
-
* has landed and been applied
|
|
11934
|
-
* where it landed
|
|
11935
|
-
* nowhere
|
|
11936
|
-
* the
|
|
12097
|
+
* When there is something to go back to: a promise resolved with `true`
|
|
12098
|
+
* once the back has landed and been applied — the document url and state
|
|
12099
|
+
* then say where it landed. A back preempted by another navigation, which
|
|
12100
|
+
* lands nowhere, resolves `false` only under the Navigation API integration
|
|
12101
|
+
* (see USE_NAVIGATION_API, off today): the History API does not tell it
|
|
12102
|
+
* apart. `undefined` otherwise — nothing happened, or the fallback took the
|
|
12103
|
+
* current entry's place.
|
|
11937
12104
|
*/
|
|
11938
12105
|
const navBack = browserIntegration.navBack;
|
|
11939
12106
|
const navForward = browserIntegration.navForward;
|
|
@@ -12870,10 +13037,11 @@ const isKeyboardModality = () => keyboardNavigationUsed;
|
|
|
12870
13037
|
{
|
|
12871
13038
|
// We implement :focus and :focus-visible with enriched semantics:
|
|
12872
13039
|
// an element is considered focused not only when it natively has focus, but also
|
|
12873
|
-
// when a
|
|
12874
|
-
//
|
|
12875
|
-
//
|
|
12876
|
-
// while the input is
|
|
13040
|
+
// when it is a control proxy whose real control has focus (the proxy is what is
|
|
13041
|
+
// seen, the focus is dealt to the real control — see control_proxy.js) or when
|
|
13042
|
+
// a controlling element has focus (e.g. a combobox input with aria-controls
|
|
13043
|
+
// pointing to a listbox — the listbox should appear focused while the input is
|
|
13044
|
+
// focused).
|
|
12877
13045
|
//
|
|
12878
13046
|
// We intentionally reuse the native :focus / :focus-visible names rather than
|
|
12879
13047
|
// introducing new navi-specific pseudo-classes (e.g. :-navi-focus). This is a
|
|
@@ -12965,7 +13133,7 @@ const isKeyboardModality = () => keyboardNavigationUsed;
|
|
|
12965
13133
|
|
|
12966
13134
|
// Returns true when el holds focus indirectly — either because a controlling
|
|
12967
13135
|
// element (aria-controls) has focus, or because el is a proxy whose target
|
|
12968
|
-
// is itself controlled by a focused element.
|
|
13136
|
+
// has focus or is itself controlled by a focused element.
|
|
12969
13137
|
const hasIndirectFocus = (el, { requireFocusVisible = false } = {}) => {
|
|
12970
13138
|
// No ring inheritance without a keyboard: an editable target draws its own
|
|
12971
13139
|
// ring on any focus (see isMatchingFocusVisible), but propagating that ring
|
|
@@ -21488,10 +21656,11 @@ const swipeTypeOf = (axis, pulled) => {
|
|
|
21488
21656
|
* insists.
|
|
21489
21657
|
*
|
|
21490
21658
|
* So the interaction stays declared and says "refuse" in place of what it does.
|
|
21491
|
-
* The threshold is the same one — a mouse travelling
|
|
21492
|
-
*
|
|
21493
|
-
*
|
|
21494
|
-
*
|
|
21659
|
+
* The threshold is the same one — a mouse travelling a few pixels, a finger
|
|
21660
|
+
* holding still, or travelling those same few pixels inside a
|
|
21661
|
+
* `data-drag-on-contact` — and at the instant the grab would have been
|
|
21662
|
+
* acquired there is none: nothing translates, no copy is made, no release is
|
|
21663
|
+
* answered. `refuse` is that instant.
|
|
21495
21664
|
*
|
|
21496
21665
|
* It walks no axis, so it takes none: a box that travels and a surface that pans
|
|
21497
21666
|
* both step over an element that is refusing, and a gesture starting on a locked
|
|
@@ -25102,8 +25271,9 @@ const createResourceLifecycleManager = () => {
|
|
|
25102
25271
|
}
|
|
25103
25272
|
|
|
25104
25273
|
// scopedMany auto-dependency: only rerun parent singular GET on child POST,
|
|
25105
|
-
//
|
|
25106
|
-
// inside its response
|
|
25274
|
+
// only the GET of the owner the child was posted to, and only when that
|
|
25275
|
+
// GET previously returned the sub-resource embedded inside its response
|
|
25276
|
+
// (detected via action._resultProperties).
|
|
25107
25277
|
// GET_MANY is excluded — a list of parents is not stale just because one
|
|
25108
25278
|
// child item was added to one of them.
|
|
25109
25279
|
scoped_many_effect: {
|
|
@@ -25124,6 +25294,15 @@ const createResourceLifecycleManager = () => {
|
|
|
25124
25294
|
if (parentResource !== resourceScope) {
|
|
25125
25295
|
continue;
|
|
25126
25296
|
}
|
|
25297
|
+
if (
|
|
25298
|
+
!isOwnerReadBy(
|
|
25299
|
+
triggerResourceScope,
|
|
25300
|
+
triggeringAction,
|
|
25301
|
+
actionCandidate,
|
|
25302
|
+
)
|
|
25303
|
+
) {
|
|
25304
|
+
break scoped_many_effect;
|
|
25305
|
+
}
|
|
25127
25306
|
// Only rerun if the last GET response included the embedded sub-resource.
|
|
25128
25307
|
if (
|
|
25129
25308
|
!getActionResultProperties(actionCandidate)?.has(propertyName)
|
|
@@ -25240,6 +25419,27 @@ const isParamSubset = (parentParams, childParams) => {
|
|
|
25240
25419
|
return true;
|
|
25241
25420
|
};
|
|
25242
25421
|
|
|
25422
|
+
// Whether a GET of the owner read the item a scoped child's action was answered
|
|
25423
|
+
// for. The child's value names its owner first ([ownerKey, …], see scopedMany);
|
|
25424
|
+
// the GET's value is the id of the item it read. The key may be a unique key
|
|
25425
|
+
// rather than that id: both lead to the owner's one store of children. Values
|
|
25426
|
+
// are read off the signals: the child's action is still completing, and its
|
|
25427
|
+
// `value` property is only written once that batch ends.
|
|
25428
|
+
const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
|
|
25429
|
+
const childValue = childAction.valueSignal.peek();
|
|
25430
|
+
if (!Array.isArray(childValue)) {
|
|
25431
|
+
return false;
|
|
25432
|
+
}
|
|
25433
|
+
const [ownerKey] = childValue;
|
|
25434
|
+
const ownerId = ownerGetAction.valueSignal.peek();
|
|
25435
|
+
const { getChildStore } = childResource;
|
|
25436
|
+
if (!getChildStore) {
|
|
25437
|
+
return ownerKey === ownerId;
|
|
25438
|
+
}
|
|
25439
|
+
const childStore = getChildStore(ownerKey);
|
|
25440
|
+
return childStore !== undefined && childStore === getChildStore(ownerId);
|
|
25441
|
+
};
|
|
25442
|
+
|
|
25243
25443
|
const paramScopeWeakSet = createIterableWeakSet();
|
|
25244
25444
|
let paramScopeIdCounter = 0;
|
|
25245
25445
|
const getParamScope = (params) => {
|
|
@@ -33741,11 +33941,13 @@ const bandTheCutMayBeRelaxedTo = (bandBefore, bandAfter, rectAfter) => {
|
|
|
33741
33941
|
* where they are going, at the movement's own pace, each animation at the rate
|
|
33742
33942
|
* that gets it there in that time.
|
|
33743
33943
|
*
|
|
33744
|
-
* Where they visibly stand is computed from the clock THROUGH the easing curve
|
|
33745
|
-
*
|
|
33746
|
-
*
|
|
33747
|
-
*
|
|
33748
|
-
*
|
|
33944
|
+
* Where they visibly stand is computed from the clock THROUGH the easing curve:
|
|
33945
|
+
* a fraction of the way, whatever property carries it — getComputedStyle on
|
|
33946
|
+
* the pseudo-elements gives the animated value of each property, with nothing
|
|
33947
|
+
* saying how far along its keyframes that value is. The rates are handed over
|
|
33948
|
+
* with updatePlaybackRate: in Chromium, the playbackRate setter given a
|
|
33949
|
+
* negative rate puts the pictures at their start at once, and leaves them
|
|
33950
|
+
* there while the clock counts down.
|
|
33749
33951
|
*/
|
|
33750
33952
|
|
|
33751
33953
|
const viewTransitionAnimations = () => {
|
|
@@ -34330,11 +34532,11 @@ const css$12 = /* css */`@property --navi-route-transition-leave {
|
|
|
34330
34532
|
*
|
|
34331
34533
|
* It is a real box, and it must be: what is photographed and clipped IS its
|
|
34332
34534
|
* rectangle. So `display: contents` cannot be used on it — an element with no
|
|
34333
|
-
* box is never captured, the movement plays on nothing
|
|
34334
|
-
* the
|
|
34335
|
-
* so `flex`, `className`, `style` and the rest are there for
|
|
34336
|
-
* application that already has an element holding its pages can mark
|
|
34337
|
-
* with `data-navi-route-transition-area` rather than nesting another.
|
|
34535
|
+
* box is never captured, and the movement then plays on nothing (navi warns
|
|
34536
|
+
* once the pictures are taken). Give it the layout the pages need instead —
|
|
34537
|
+
* it is a `Box`, so `flex`, `className`, `style` and the rest are there for
|
|
34538
|
+
* that. An application that already has an element holding its pages can mark
|
|
34539
|
+
* that one with `data-navi-route-transition-area` rather than nesting another.
|
|
34338
34540
|
*
|
|
34339
34541
|
* @type {import("ignore:preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
34340
34542
|
*/
|
|
@@ -34395,10 +34597,14 @@ const RouteTransitionArea = ({
|
|
|
34395
34597
|
* length of the transition the root carries
|
|
34396
34598
|
* `data-navi-route-transition-type="<type>"` next to
|
|
34397
34599
|
* `data-navi-route-transition="forward"|"back"`, and the application's CSS
|
|
34398
|
-
* defines the movement against the view transition pseudo-elements
|
|
34600
|
+
* defines the movement against the view transition pseudo-elements, for
|
|
34601
|
+
* both groups: `root` when the document travels, `navi-route-transition`
|
|
34602
|
+
* when an area is marked — the root is then unnamed, and a rule written for
|
|
34603
|
+
* `(root)` alone never applies:
|
|
34399
34604
|
*
|
|
34400
34605
|
* :root[data-navi-route-transition-type="spin"][data-navi-route-transition="forward"] {
|
|
34401
|
-
* &::view-transition-new(root)
|
|
34606
|
+
* &::view-transition-new(root),
|
|
34607
|
+
* &::view-transition-new(navi-route-transition) {
|
|
34402
34608
|
* animation-name: my-spin-in;
|
|
34403
34609
|
* }
|
|
34404
34610
|
* }
|
|
@@ -35056,7 +35262,7 @@ const beginTransition = ({
|
|
|
35056
35262
|
// and the movement then plays on the document itself.
|
|
35057
35263
|
const areaElements = document.querySelectorAll(`[${TRANSITION_AREA_ATTRIBUTE}]`);
|
|
35058
35264
|
if (areaElements.length > 1) {
|
|
35059
|
-
warnOnce("several-areas", `${areaElements.length} elements carry ${TRANSITION_AREA_ATTRIBUTE}. They all take the same view-transition-name, and a name belongs to one element at a time: the browser refuses EVERY
|
|
35265
|
+
warnOnce("several-areas", `${areaElements.length} elements carry ${TRANSITION_AREA_ATTRIBUTE}. They all take the same view-transition-name, and a name belongs to one element at a time: the browser refuses EVERY route transition while this holds. Mark the one element the pages live in.`);
|
|
35060
35266
|
}
|
|
35061
35267
|
const areaElement = areaElements.length > 0 ? areaElements[0] : null;
|
|
35062
35268
|
// Everything about to be photographed goes deaf to the pointer; the controls
|
|
@@ -35800,8 +36006,9 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
35800
36006
|
* - how to go to a tab. The default REPLACES the current history entry
|
|
35801
36007
|
* rather than pushing one: a swipe is how one browses a page, not a place one
|
|
35802
36008
|
* aimed at, and three swipes back and forth must not bury the way out of the
|
|
35803
|
-
* page under six entries. A tab pressed
|
|
35804
|
-
*
|
|
36009
|
+
* page under six entries. A tab pressed goes the way its <Link> says —
|
|
36010
|
+
* `<Link replace>` for a row of tabs, so a press and a swipe towards the
|
|
36011
|
+
* same neighbour write the same history.
|
|
35805
36012
|
*
|
|
35806
36013
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`,
|
|
35807
36014
|
* …), which is how one of these is named: a page can hold several — a row of
|
|
@@ -35816,10 +36023,11 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
35816
36023
|
* gesture applies its change on release instead of dragging a picture that does
|
|
35817
36024
|
* not exist.
|
|
35818
36025
|
*
|
|
35819
|
-
* While a travel plays, the rest of the page is taken as a picture
|
|
35820
|
-
*
|
|
35821
|
-
*
|
|
35822
|
-
*
|
|
36026
|
+
* While a travel plays, the rest of the page is NOT taken as a picture: the
|
|
36027
|
+
* root's name is dropped for that time (`view-transition-name: none`), so what
|
|
36028
|
+
* stands around the box — a tab row beside it — stays live and answers a press
|
|
36029
|
+
* mid-travel. The application's own rule for the root applies again the moment
|
|
36030
|
+
* the travel is over.
|
|
35823
36031
|
*/
|
|
35824
36032
|
const RouteTravel = ({
|
|
35825
36033
|
routes: routesProp,
|
|
@@ -37008,9 +37216,9 @@ const writeCover = (edge, element, size) => {
|
|
|
37008
37216
|
|
|
37009
37217
|
/**
|
|
37010
37218
|
* Binds an action to a route: it runs when the route matches, with the params
|
|
37011
|
-
* the effect reads off the address,
|
|
37012
|
-
* Its data, its wait and its failure are read by the
|
|
37013
|
-
* `useAsyncData`.
|
|
37219
|
+
* the effect reads off the address, runs again on a `reload()`, and is aborted
|
|
37220
|
+
* when the route is left. Its data, its wait and its failure are read by the
|
|
37221
|
+
* page through `useAsyncData`.
|
|
37014
37222
|
*
|
|
37015
37223
|
* A page's CODE is declared the same way — the import is one more thing the
|
|
37016
37224
|
* address asks for, started with the data rather than after it:
|
|
@@ -37054,18 +37262,21 @@ const routeAction = (
|
|
|
37054
37262
|
? routes[0].matchingSignal
|
|
37055
37263
|
: anyMatchingRouteSignal(routes);
|
|
37056
37264
|
const readParams = paramsEffect || (() => true);
|
|
37057
|
-
const
|
|
37058
|
-
|
|
37059
|
-
()
|
|
37060
|
-
|
|
37061
|
-
|
|
37062
|
-
|
|
37063
|
-
|
|
37064
|
-
|
|
37065
|
-
|
|
37066
|
-
|
|
37067
|
-
|
|
37068
|
-
|
|
37265
|
+
const readParamsAsked = () => {
|
|
37266
|
+
const matching = routeMatchingSignal.value;
|
|
37267
|
+
const params = readParams();
|
|
37268
|
+
if (!matching) {
|
|
37269
|
+
return null;
|
|
37270
|
+
}
|
|
37271
|
+
return params;
|
|
37272
|
+
};
|
|
37273
|
+
const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
|
|
37274
|
+
for (const route of routes) {
|
|
37275
|
+
// Asked of the route's params as they are, like the effect asks them.
|
|
37276
|
+
registerRouteReload(route, () =>
|
|
37277
|
+
untracked(readParamsAsked) ? actionBoundToRoute : null,
|
|
37278
|
+
);
|
|
37279
|
+
}
|
|
37069
37280
|
if (prefetch) {
|
|
37070
37281
|
for (const route of routes) {
|
|
37071
37282
|
if (paramsEffect) {
|
|
@@ -38999,10 +39210,11 @@ const flushSyncRendering = (fn) => {
|
|
|
38999
39210
|
* without the content, and the browser builds it in an idle moment after
|
|
39000
39211
|
* load — so by the time anyone clicks, it is usually already there.
|
|
39001
39212
|
*
|
|
39002
|
-
* "while-opened" is the opposite end: content
|
|
39003
|
-
*
|
|
39004
|
-
*
|
|
39005
|
-
*
|
|
39213
|
+
* "while-opened" is the opposite end: content rebuilt from scratch every
|
|
39214
|
+
* time, a form whose fresh state is its initial state. What kept content
|
|
39215
|
+
* carries into the next opening is an unsent edit — an untouched field
|
|
39216
|
+
* follows a new `defaultValue` on its own (see followDefaultValue in
|
|
39217
|
+
* ui_state_controller.js) — and this is what throws that edit away.
|
|
39006
39218
|
*
|
|
39007
39219
|
* On top of whichever value is picked, intent on the anchor warms the content:
|
|
39008
39220
|
* a pointer entering the popup's anchor, or focus landing in it, builds the
|
|
@@ -39036,9 +39248,11 @@ const flushSyncRendering = (fn) => {
|
|
|
39036
39248
|
* click coming after the release, the browser taking the gesture
|
|
39037
39249
|
* (`pointercancel`), or the next press starting, before any of its own
|
|
39038
39250
|
* handlers run: one press at a time holds a content. A keyboard opening builds
|
|
39039
|
-
* at open time. A popup with an `onOpen` is
|
|
39040
|
-
* before the content is built (see
|
|
39041
|
-
* itself from what it writes must
|
|
39251
|
+
* at open time. A "while-opened" popup with an `onOpen` is not warmed by the
|
|
39252
|
+
* press: that callback runs before the content is built (see
|
|
39253
|
+
* open_controller.js), and a content seeding itself from what it writes must
|
|
39254
|
+
* be built after it. Hover and focus warming, for the other values, builds
|
|
39255
|
+
* ahead of `onOpen` all the same.
|
|
39042
39256
|
*/
|
|
39043
39257
|
|
|
39044
39258
|
|
|
@@ -49646,12 +49860,13 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
49646
49860
|
* does so by being NAMED, which is all the browser needs: any change played as
|
|
49647
49861
|
* a view transition animates it on the same clock as everything else in that
|
|
49648
49862
|
* transition. Inside a `RouteTravel` that means it follows the pages, and the
|
|
49649
|
-
* thumb dragging them, without either of them being told about the other.
|
|
49650
|
-
*
|
|
49651
|
-
*
|
|
49652
|
-
* arrives with its row.
|
|
49653
|
-
*
|
|
49654
|
-
*
|
|
49863
|
+
* thumb dragging them, without either of them being told about the other.
|
|
49864
|
+
* During a route transition or a route travel it keeps the name only for a
|
|
49865
|
+
* movement between two tabs of the row: when the row is on one side only,
|
|
49866
|
+
* the bar leaves or arrives with its row. Any other view transition finds it
|
|
49867
|
+
* named, a picture of its own. For a nav made of slides (`slideContainer`)
|
|
49868
|
+
* the bar is one element for the whole row, and it reads the travel the
|
|
49869
|
+
* container publishes.
|
|
49655
49870
|
* @param {string} [props.slideContainer] - the id of a `<SlideContainer>` these
|
|
49656
49871
|
* tabs are about: each one says which slide it is (`<Link slide="…">`), the
|
|
49657
49872
|
* container says which one is on screen, and pressing a tab travels there.
|
|
@@ -54160,7 +54375,8 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
54160
54375
|
* `command` runs at once — the popup it sits in closes over the run, which
|
|
54161
54376
|
* goes on behind. A failure then has no popup to be read in: `resetOnError`
|
|
54162
54377
|
* puts the button back and the error callout is drawn on what surrounds the
|
|
54163
|
-
* closed popup (see
|
|
54378
|
+
* closed popup (see
|
|
54379
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
54164
54380
|
* @param {boolean} [actionAbortable] The person waiting may give up on this
|
|
54165
54381
|
* button's action: closing the popup the run holds calls it off and goes
|
|
54166
54382
|
* through, instead of being refused. For a run whose answer may never come —
|
|
@@ -64008,7 +64224,7 @@ const css$E = /* css */`
|
|
|
64008
64224
|
the length of the movement, and the picture of the lifted node comes out
|
|
64009
64225
|
empty (Chrome 153, reproduced in a bare page). The price is a page frozen
|
|
64010
64226
|
and unpressable while the movement plays: under a modal wall at the
|
|
64011
|
-
opening, and for
|
|
64227
|
+
opening, and for the popup's --popup-animation-duration at the closing. */
|
|
64012
64228
|
:root[data-navi-popup-lift] {
|
|
64013
64229
|
/* The popup's own pace, published on the root by popup_lift.js because the
|
|
64014
64230
|
::view-transition tree hangs off it and inherits from nowhere else. */
|
|
@@ -64352,16 +64568,21 @@ const css$E = /* css */`
|
|
|
64352
64568
|
* @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
|
|
64353
64569
|
* Where the keyboard goes when this dialog opens — one rung of the ladder in
|
|
64354
64570
|
* `docs/autofocus.md`, which is what to read for the whole of it.
|
|
64355
|
-
* - `true` — the dialog element itself takes the keyboard
|
|
64356
|
-
*
|
|
64357
|
-
*
|
|
64571
|
+
* - `true` — the dialog element itself takes the keyboard rather than the
|
|
64572
|
+
* first focusable it holds; something inside that asks by name
|
|
64573
|
+
* (`autoFocus`) still comes first. For a dialog whose content is READ
|
|
64574
|
+
* before it is filled: the focus starts at the top of the reading
|
|
64575
|
+
* order and no virtual keyboard rises over it.
|
|
64358
64576
|
* - `"last-resort"` — the dialog takes the keyboard only if it holds nothing
|
|
64359
64577
|
* focusable of its own.
|
|
64360
64578
|
* - `"restore"` — the dialog stays out of the opening focus chain unless it
|
|
64361
64579
|
* held focus when it closed.
|
|
64362
|
-
* - `false` — the dialog
|
|
64363
|
-
*
|
|
64364
|
-
*
|
|
64580
|
+
* - `false` — the dialog says nothing, and the opening still hands out the
|
|
64581
|
+
* focus: a `<dialog>` is focusable and the first focusable of its own
|
|
64582
|
+
* subtree, so it takes the keyboard as with `true`. Where the first
|
|
64583
|
+
* focusable is skipped (a touch device, below), a last resort inside is
|
|
64584
|
+
* tried instead, and nothing there leaves the keyboard where it was.
|
|
64585
|
+
* Unlike `Popover`'s `false`, the transfer runs.
|
|
64365
64586
|
* Wherever the keyboard is a virtual one (a touch device), the surface is
|
|
64366
64587
|
* already what one arrives on: a popup is read before it is reached there, so
|
|
64367
64588
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
@@ -64395,9 +64616,9 @@ const css$E = /* css */`
|
|
|
64395
64616
|
* is "new" or "edit X" depending on the press must know which one it is
|
|
64396
64617
|
* before what it holds is rendered.
|
|
64397
64618
|
* @param {(event: Event) => void} [props.onClose] - Called when the dialog
|
|
64398
|
-
* actually closes — not preventable
|
|
64399
|
-
*
|
|
64400
|
-
*
|
|
64619
|
+
* actually closes — not preventable: it is the close happening, not a
|
|
64620
|
+
* request. What refuses a close is navi's: a control inside still
|
|
64621
|
+
* running its action (see popup_busy.js for the runs that let it through).
|
|
64401
64622
|
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
64402
64623
|
* Keeps the open state in the history entry, so a screen left and come back
|
|
64403
64624
|
* to finds this popup as it was — open, and without an entrance playing: it
|
|
@@ -64423,8 +64644,9 @@ const css$E = /* css */`
|
|
|
64423
64644
|
* `"idle"` builds them in a browser idle moment after load — "always" minus
|
|
64424
64645
|
* the cost on the critical render. `"while-opened"` throws them away once
|
|
64425
64646
|
* the popup has finished closing, for content whose fresh state is its
|
|
64426
|
-
* initial state: an
|
|
64427
|
-
*
|
|
64647
|
+
* initial state: an edit left unsent at the close does not come back at
|
|
64648
|
+
* the next opening (an untouched field needs none of this to show a new
|
|
64649
|
+
* `defaultValue` — it follows one). Intent on the anchor (pointer
|
|
64428
64650
|
* entering it, focus landing in it) builds the content ahead of the click;
|
|
64429
64651
|
* under `"while-opened"`, only a press on what opens the popup does (a
|
|
64430
64652
|
* `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
|
|
@@ -65093,10 +65315,10 @@ const useDialogProps = props => {
|
|
|
65093
65315
|
}
|
|
65094
65316
|
|
|
65095
65317
|
// Suppressed until committed below — same @starting-style-avoidance
|
|
65096
|
-
// reasoning as popover.jsx's own openEffect (see
|
|
65097
|
-
// though Dialog never needs to measure/flip anything: it
|
|
65098
|
-
// genuinely rendered "closed" frame to transition from, not a
|
|
65099
|
-
// straight from not-shown to aria-expanded="true".
|
|
65318
|
+
// reasoning as popover.jsx's own openEffect (see popup_css.js's top
|
|
65319
|
+
// comment), even though Dialog never needs to measure/flip anything: it
|
|
65320
|
+
// still needs a genuinely rendered "closed" frame to transition from, not a
|
|
65321
|
+
// jump straight from not-shown to aria-expanded="true".
|
|
65100
65322
|
dialogEl.style.transitionProperty = "none";
|
|
65101
65323
|
if (backdropEl) {
|
|
65102
65324
|
disarmBackdropHideRef.current?.();
|
|
@@ -66173,10 +66395,11 @@ const css$D = /* css */`
|
|
|
66173
66395
|
* @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
|
|
66174
66396
|
* Where the keyboard goes when this popover opens — one rung of the ladder in
|
|
66175
66397
|
* `docs/autofocus.md`, which is what to read for the whole of it.
|
|
66176
|
-
* - `true` — the popover element itself takes the keyboard
|
|
66177
|
-
*
|
|
66178
|
-
*
|
|
66179
|
-
* it
|
|
66398
|
+
* - `true` — the popover element itself takes the keyboard rather than the
|
|
66399
|
+
* first focusable it holds; something inside that asks by name
|
|
66400
|
+
* (`autoFocus`) still comes first. For a popover whose content is READ
|
|
66401
|
+
* before it is filled: the focus starts at the top of the reading
|
|
66402
|
+
* order and no virtual keyboard rises over it.
|
|
66180
66403
|
* - `"last-resort"` — the popover takes the keyboard only if it holds nothing
|
|
66181
66404
|
* focusable of its own.
|
|
66182
66405
|
* - `"restore"` — the popover stays out of the opening focus chain unless it
|
|
@@ -66217,9 +66440,9 @@ const css$D = /* css */`
|
|
|
66217
66440
|
* is "new" or "edit X" depending on the press must know which one it is
|
|
66218
66441
|
* before what it holds is rendered.
|
|
66219
66442
|
* @param {(event: Event) => void} [props.onClose] - Called when the popover
|
|
66220
|
-
* actually closes — not preventable
|
|
66221
|
-
*
|
|
66222
|
-
*
|
|
66443
|
+
* actually closes — not preventable: it is the close happening, not a
|
|
66444
|
+
* request. What refuses a close is navi's: a control inside still
|
|
66445
|
+
* running its action (see popup_busy.js for the runs that let it through).
|
|
66223
66446
|
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
66224
66447
|
* Keeps the open state in the history entry, so a screen left and come back
|
|
66225
66448
|
* to finds this popup as it was — open, and without an entrance playing: it
|
|
@@ -66243,8 +66466,9 @@ const css$D = /* css */`
|
|
|
66243
66466
|
* `"idle"` builds them in a browser idle moment after load — "always" minus
|
|
66244
66467
|
* the cost on the critical render. `"while-opened"` throws them away once
|
|
66245
66468
|
* the popup has finished closing, for content whose fresh state is its
|
|
66246
|
-
* initial state: an
|
|
66247
|
-
*
|
|
66469
|
+
* initial state: an edit left unsent at the close does not come back at
|
|
66470
|
+
* the next opening (an untouched field needs none of this to show a new
|
|
66471
|
+
* `defaultValue` — it follows one). Intent on the anchor (pointer
|
|
66248
66472
|
* entering it, focus landing in it) builds the content ahead of the click;
|
|
66249
66473
|
* under `"while-opened"`, only a press on what opens the popup does (a
|
|
66250
66474
|
* `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
|
|
@@ -66669,7 +66893,7 @@ const usePopoverProps = props => {
|
|
|
66669
66893
|
});
|
|
66670
66894
|
|
|
66671
66895
|
// Suppressed until the popover is actually measured/positioned below —
|
|
66672
|
-
// see
|
|
66896
|
+
// see popup_css.js's top comment for why @starting-style can't drive the
|
|
66673
66897
|
// opening transition (it needs the popover's actual resting position
|
|
66674
66898
|
// already in place, which requires a layout box that only exists once
|
|
66675
66899
|
// shown).
|
|
@@ -67017,7 +67241,7 @@ const usePopoverProps = props => {
|
|
|
67017
67241
|
// frame set up above as a real rendered state (the reflow), re-enables
|
|
67018
67242
|
// transitions, flips aria-expanded="true" (only then does the CSS
|
|
67019
67243
|
// transition play, from that just-committed frame to the open one,
|
|
67020
|
-
// with no @starting-style involved at all — see
|
|
67244
|
+
// with no @starting-style involved at all — see popup_css.js's top
|
|
67021
67245
|
// comment), suppresses pointer events until it settles, and transfers
|
|
67022
67246
|
// focus in. Inlined rather than a standalone function since it only
|
|
67023
67247
|
// has this one call site.
|
|
@@ -67504,8 +67728,9 @@ const css$C = /* css */`@layer navi {
|
|
|
67504
67728
|
* `"idle"` builds them in a browser idle moment after load — `"always"`
|
|
67505
67729
|
* minus the cost on the critical render.
|
|
67506
67730
|
* `"while-opened"` throws them away once the popup has finished closing, for
|
|
67507
|
-
* content whose fresh state is its initial state: an
|
|
67508
|
-
*
|
|
67731
|
+
* content whose fresh state is its initial state: an edit left unsent at
|
|
67732
|
+
* the close does not come back at the next opening (an untouched field
|
|
67733
|
+
* needs none of this to show a new `defaultValue` — it follows one).
|
|
67509
67734
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
67510
67735
|
*/
|
|
67511
67736
|
const Popup = props => {
|
|
@@ -67804,8 +68029,9 @@ const PickerCustom = props => {
|
|
|
67804
68029
|
open,
|
|
67805
68030
|
defaultOpen,
|
|
67806
68031
|
// What Escape means for this picker. "cancel" (the default) puts back the
|
|
67807
|
-
// value the picker had at open and, for a dialog
|
|
67808
|
-
// so everything written to the url while it was open
|
|
68032
|
+
// value the picker had at open and, for a dialog with an explicit id, goes
|
|
68033
|
+
// back in history — so everything written to the url while it was open
|
|
68034
|
+
// goes back too (see pickerNavType below).
|
|
67809
68035
|
// "close" makes Escape say the same thing as clicking outside: keep what
|
|
67810
68036
|
// was chosen, close the popup.
|
|
67811
68037
|
escapeEffect = "cancel",
|
|
@@ -68459,9 +68685,10 @@ const PickerContentInsidePopup = props => {
|
|
|
68459
68685
|
// itself, and this one does not. Popover ignores it, same as Dialog ignores
|
|
68460
68686
|
// marginWithAnchor.
|
|
68461
68687
|
dockedOnSmallTouchScreen,
|
|
68462
|
-
// Same again: the dialog takes the trigger's box as a floor (
|
|
68463
|
-
//
|
|
68464
|
-
//
|
|
68688
|
+
// Same again: the dialog takes the trigger's box as a floor (Dialog's own
|
|
68689
|
+
// `sizeFromAnchor`) and, unless a dialogMaxWidth says otherwise, as a
|
|
68690
|
+
// ceiling (see maxWidth below) — what keeps a card its own width once
|
|
68691
|
+
// lifted.
|
|
68465
68692
|
dialogSizeFromAnchor,
|
|
68466
68693
|
// The caller's word on the popup's own box. Written on the popup element,
|
|
68467
68694
|
// where Popover/Dialog map them to their own vars, rather than as vars on
|