@jsenv/navi 0.29.418 → 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/README.md +1 -1
- package/dist/dev/jsenv_navi.js +584 -196
- package/dist/dev/jsenv_navi.js.map +35 -35
- package/dist/jsenv_navi.js +563 -187
- package/dist/jsenv_navi.js.map +35 -35
- package/docs/AI_INSTRUCTIONS.md +177 -261
- package/docs/actions.md +164 -251
- package/docs/autofocus.md +66 -83
- package/docs/control_object.md +117 -214
- package/docs/control_value.md +121 -185
- package/docs/create_and_edit.md +169 -254
- package/docs/css_architecture.md +196 -260
- package/docs/data_states.md +39 -43
- package/docs/dialog_shape.md +91 -134
- package/docs/drag_interactions.md +115 -174
- package/docs/drag_to_travel.md +10 -9
- package/docs/dynamic_import.md +119 -136
- package/docs/error_handling.md +75 -127
- package/docs/field_validation.md +49 -89
- package/docs/form_changed.md +66 -81
- package/docs/group.md +141 -0
- package/docs/i18n.md +31 -76
- package/docs/interactions.md +6 -4
- package/docs/list_action.md +36 -51
- package/docs/list_refresh.md +73 -115
- package/docs/mobile_touch.md +56 -62
- package/docs/navigation.md +377 -723
- package/docs/network_policy.md +61 -75
- package/docs/pan_zoom.md +62 -103
- package/docs/popup_backdrop.md +128 -168
- package/docs/popup_lift.md +179 -268
- package/docs/popup_open.md +380 -651
- package/docs/resource.md +228 -408
- package/docs/route_transitions.md +333 -482
- package/docs/safe_area.md +102 -89
- package/docs/scroll.md +175 -335
- package/docs/state_binding.md +2 -1
- package/docs/testid.md +18 -23
- package/docs/typography.md +111 -141
- package/docs/view_transitions.md +106 -104
- package/docs/z_index.md +61 -109
- package/package.json +2 -2
- package/docs/badge_list.md +0 -83
- package/docs/control_group.md +0 -168
- package/docs/mobile_layout_pitfalls.md +0 -145
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
|
|
@@ -14476,7 +14644,11 @@ const createOpenController = (
|
|
|
14476
14644
|
const applyChange = () => {
|
|
14477
14645
|
// Recorded with the change itself: what the DOM shows is what a render
|
|
14478
14646
|
// landing between the ask and the picture must draw (see `openedInDom`).
|
|
14479
|
-
|
|
14647
|
+
// An opening records it inside the change, once its content is built
|
|
14648
|
+
// (see open() below).
|
|
14649
|
+
if (!opened) {
|
|
14650
|
+
controller.openedInDom = false;
|
|
14651
|
+
}
|
|
14480
14652
|
change();
|
|
14481
14653
|
};
|
|
14482
14654
|
const { transitionChange } = controller;
|
|
@@ -14756,15 +14928,17 @@ const createOpenController = (
|
|
|
14756
14928
|
controller.mountContent?.();
|
|
14757
14929
|
// Only now — after the content has been built, before openEffect
|
|
14758
14930
|
// shows it. Dialog/Popover recompute aria-expanded and navi-hidden
|
|
14759
|
-
// from
|
|
14931
|
+
// from `openedInDom` on every render, and mountContent above renders
|
|
14760
14932
|
// synchronously: flipping it any earlier commits an already-open DOM
|
|
14761
14933
|
// (aria-expanded "true", navi-hidden gone) before openEffect has run
|
|
14762
14934
|
// a single statement, so the "closed" frame it pins to transition
|
|
14763
14935
|
// from is in fact the open one and the entrance animation has
|
|
14764
|
-
// nothing to play
|
|
14765
|
-
//
|
|
14766
|
-
//
|
|
14767
|
-
// popup_content_mount.js and
|
|
14936
|
+
// nothing to play — on every first opening that builds its content.
|
|
14937
|
+
// It also gives the content it just built the opening it is
|
|
14938
|
+
// documented to observe — mounted while the popup reads as closed,
|
|
14939
|
+
// told it opened right after (see popup_content_mount.js and
|
|
14940
|
+
// use_displayed_layout_effect.js).
|
|
14941
|
+
controller.openedInDom = true;
|
|
14768
14942
|
controller.opened = true;
|
|
14769
14943
|
// Which press it opened during, so the release of that press is not
|
|
14770
14944
|
// read as somebody dismissing it (see openedDuringThisPress).
|
|
@@ -15155,7 +15329,14 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
15155
15329
|
lastRunOpenRef.current = open;
|
|
15156
15330
|
|
|
15157
15331
|
if (isFirstRun) {
|
|
15158
|
-
|
|
15332
|
+
// "interaction" is said even over what the caller holds reading open: a
|
|
15333
|
+
// popup built by the request that opens it — a dialog standing in until
|
|
15334
|
+
// its signal said open, see DialogStandIn in dialog.jsx — mounts BECAUSE
|
|
15335
|
+
// of that opening.
|
|
15336
|
+
const mountOpenReason =
|
|
15337
|
+
open && defaultOpen === "interaction"
|
|
15338
|
+
? "interaction"
|
|
15339
|
+
: open || defaultOpen;
|
|
15159
15340
|
if (mountOpenReason) {
|
|
15160
15341
|
// Whether this popup being open is something that just happened, or
|
|
15161
15342
|
// something that was already true when the page appeared. "interaction"
|
|
@@ -21475,10 +21656,11 @@ const swipeTypeOf = (axis, pulled) => {
|
|
|
21475
21656
|
* insists.
|
|
21476
21657
|
*
|
|
21477
21658
|
* So the interaction stays declared and says "refuse" in place of what it does.
|
|
21478
|
-
* The threshold is the same one — a mouse travelling
|
|
21479
|
-
*
|
|
21480
|
-
*
|
|
21481
|
-
*
|
|
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.
|
|
21482
21664
|
*
|
|
21483
21665
|
* It walks no axis, so it takes none: a box that travels and a surface that pans
|
|
21484
21666
|
* both step over an element that is refusing, and a gesture starting on a locked
|
|
@@ -25089,8 +25271,9 @@ const createResourceLifecycleManager = () => {
|
|
|
25089
25271
|
}
|
|
25090
25272
|
|
|
25091
25273
|
// scopedMany auto-dependency: only rerun parent singular GET on child POST,
|
|
25092
|
-
//
|
|
25093
|
-
// 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).
|
|
25094
25277
|
// GET_MANY is excluded — a list of parents is not stale just because one
|
|
25095
25278
|
// child item was added to one of them.
|
|
25096
25279
|
scoped_many_effect: {
|
|
@@ -25111,6 +25294,15 @@ const createResourceLifecycleManager = () => {
|
|
|
25111
25294
|
if (parentResource !== resourceScope) {
|
|
25112
25295
|
continue;
|
|
25113
25296
|
}
|
|
25297
|
+
if (
|
|
25298
|
+
!isOwnerReadBy(
|
|
25299
|
+
triggerResourceScope,
|
|
25300
|
+
triggeringAction,
|
|
25301
|
+
actionCandidate,
|
|
25302
|
+
)
|
|
25303
|
+
) {
|
|
25304
|
+
break scoped_many_effect;
|
|
25305
|
+
}
|
|
25114
25306
|
// Only rerun if the last GET response included the embedded sub-resource.
|
|
25115
25307
|
if (
|
|
25116
25308
|
!getActionResultProperties(actionCandidate)?.has(propertyName)
|
|
@@ -25227,6 +25419,27 @@ const isParamSubset = (parentParams, childParams) => {
|
|
|
25227
25419
|
return true;
|
|
25228
25420
|
};
|
|
25229
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
|
+
|
|
25230
25443
|
const paramScopeWeakSet = createIterableWeakSet();
|
|
25231
25444
|
let paramScopeIdCounter = 0;
|
|
25232
25445
|
const getParamScope = (params) => {
|
|
@@ -27804,6 +28017,18 @@ const useAsyncData = (promiseOrAction, {
|
|
|
27804
28017
|
|
|
27805
28018
|
// ─── useAction ────────────────────────────────────────────────────────────────
|
|
27806
28019
|
|
|
28020
|
+
// subscribe() calls back at once with the value the signal holds; what these
|
|
28021
|
+
// subscriptions are for is what changes after.
|
|
28022
|
+
const subscribeToChanges = (signal, onChange) => {
|
|
28023
|
+
let initial = true;
|
|
28024
|
+
return signal.subscribe(value => {
|
|
28025
|
+
if (initial) {
|
|
28026
|
+
initial = false;
|
|
28027
|
+
return;
|
|
28028
|
+
}
|
|
28029
|
+
onChange(value);
|
|
28030
|
+
});
|
|
28031
|
+
};
|
|
27807
28032
|
const LoadingContext = createContext(null);
|
|
27808
28033
|
const actionPendingPromiseWeakMap = new WeakMap();
|
|
27809
28034
|
const dismissedActionWeakSet = new WeakSet();
|
|
@@ -27848,6 +28073,15 @@ const useActionAsyncData = (action, {
|
|
|
27848
28073
|
const runningState = action.runningStateSignal.peek();
|
|
27849
28074
|
const [, setTick] = useState(0);
|
|
27850
28075
|
const routePage = useContext(RoutePageContext);
|
|
28076
|
+
// What this render read of the action, for the subscriptions below to tell
|
|
28077
|
+
// a change that came between the render and them from none at all.
|
|
28078
|
+
const readRef = useRef(null);
|
|
28079
|
+
readRef.current = {
|
|
28080
|
+
runningState,
|
|
28081
|
+
data: action.dataSignal.peek(),
|
|
28082
|
+
params: action.paramsSignal.peek(),
|
|
28083
|
+
settling: action.paramsSettlingSignal.peek()
|
|
28084
|
+
};
|
|
27851
28085
|
useEffect(() => {
|
|
27852
28086
|
let stopOwingRender = null;
|
|
27853
28087
|
const rerender = () => {
|
|
@@ -27875,7 +28109,12 @@ const useActionAsyncData = (action, {
|
|
|
27875
28109
|
}
|
|
27876
28110
|
setTick(n => n + 1);
|
|
27877
28111
|
};
|
|
27878
|
-
|
|
28112
|
+
// A run under way is a new try: an error dismissed before it is not the
|
|
28113
|
+
// one it may end on.
|
|
28114
|
+
if (action.runningStateSignal.peek() === RUNNING) {
|
|
28115
|
+
dismissedActionWeakSet.delete(action);
|
|
28116
|
+
}
|
|
28117
|
+
const unsubscribeFromRunningState = subscribeToChanges(action.runningStateSignal, state => {
|
|
27879
28118
|
if (state === RUNNING) {
|
|
27880
28119
|
dismissedActionWeakSet.delete(action);
|
|
27881
28120
|
}
|
|
@@ -27886,38 +28125,24 @@ const useActionAsyncData = (action, {
|
|
|
27886
28125
|
// (a PUT upserting an item that a GET_MANY list already holds) changes the
|
|
27887
28126
|
// data while this action stays COMPLETED. Subscribing here re-renders
|
|
27888
28127
|
// through the same controlled path as the run state, instead of `.value`.
|
|
27889
|
-
|
|
27890
|
-
const unsubscribeFromData = action.dataSignal.subscribe(() => {
|
|
27891
|
-
if (dataNotificationIsInitial) {
|
|
27892
|
-
// subscribe() calls back synchronously with the current value
|
|
27893
|
-
dataNotificationIsInitial = false;
|
|
27894
|
-
return;
|
|
27895
|
-
}
|
|
27896
|
-
rerender();
|
|
27897
|
-
});
|
|
28128
|
+
const unsubscribeFromData = subscribeToChanges(action.dataSignal, rerender);
|
|
27898
28129
|
// The params say WHICH question this is, and this hook reads them: to know
|
|
27899
28130
|
// there is nothing to ask for, and to start the run it owns. A binding
|
|
27900
28131
|
// retargeting from no question to a question — a filter chosen, a first
|
|
27901
28132
|
// character typed — is announced by nothing above: a fresh target is IDLE
|
|
27902
28133
|
// holding no data, the very state the hook already sees.
|
|
27903
|
-
|
|
27904
|
-
const unsubscribeFromParams = action.paramsSignal.subscribe(() => {
|
|
27905
|
-
if (paramsNotificationIsInitial) {
|
|
27906
|
-
paramsNotificationIsInitial = false;
|
|
27907
|
-
return;
|
|
27908
|
-
}
|
|
27909
|
-
rerender();
|
|
27910
|
-
});
|
|
28134
|
+
const unsubscribeFromParams = subscribeToChanges(action.paramsSignal, rerender);
|
|
27911
28135
|
// A debounced binding waits before it retargets, so nothing above changes
|
|
27912
28136
|
// while the delay runs — but what is on screen is already out of date.
|
|
27913
|
-
|
|
27914
|
-
|
|
27915
|
-
|
|
27916
|
-
|
|
27917
|
-
|
|
27918
|
-
|
|
28137
|
+
const unsubscribeFromParamsSettling = subscribeToChanges(action.paramsSettlingSignal, rerender);
|
|
28138
|
+
// Subscribed once the browser has painted, so the action may have moved
|
|
28139
|
+
// since the render: a run that very render started may be over already,
|
|
28140
|
+
// and nothing would say so again. What the render read, against what is
|
|
28141
|
+
// there now — and nothing moved, nothing to draw again.
|
|
28142
|
+
const read = readRef.current;
|
|
28143
|
+
if (action.runningStateSignal.peek() !== read.runningState || action.dataSignal.peek() !== read.data || action.paramsSignal.peek() !== read.params || action.paramsSettlingSignal.peek() !== read.settling) {
|
|
27919
28144
|
rerender();
|
|
27920
|
-
}
|
|
28145
|
+
}
|
|
27921
28146
|
return () => {
|
|
27922
28147
|
unsubscribeFromRunningState();
|
|
27923
28148
|
unsubscribeFromData();
|
|
@@ -33716,11 +33941,13 @@ const bandTheCutMayBeRelaxedTo = (bandBefore, bandAfter, rectAfter) => {
|
|
|
33716
33941
|
* where they are going, at the movement's own pace, each animation at the rate
|
|
33717
33942
|
* that gets it there in that time.
|
|
33718
33943
|
*
|
|
33719
|
-
* Where they visibly stand is computed from the clock THROUGH the easing curve
|
|
33720
|
-
*
|
|
33721
|
-
*
|
|
33722
|
-
*
|
|
33723
|
-
*
|
|
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.
|
|
33724
33951
|
*/
|
|
33725
33952
|
|
|
33726
33953
|
const viewTransitionAnimations = () => {
|
|
@@ -34305,11 +34532,11 @@ const css$12 = /* css */`@property --navi-route-transition-leave {
|
|
|
34305
34532
|
*
|
|
34306
34533
|
* It is a real box, and it must be: what is photographed and clipped IS its
|
|
34307
34534
|
* rectangle. So `display: contents` cannot be used on it — an element with no
|
|
34308
|
-
* box is never captured, the movement plays on nothing
|
|
34309
|
-
* the
|
|
34310
|
-
* so `flex`, `className`, `style` and the rest are there for
|
|
34311
|
-
* application that already has an element holding its pages can mark
|
|
34312
|
-
* 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.
|
|
34313
34540
|
*
|
|
34314
34541
|
* @type {import("ignore:preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
34315
34542
|
*/
|
|
@@ -34370,10 +34597,14 @@ const RouteTransitionArea = ({
|
|
|
34370
34597
|
* length of the transition the root carries
|
|
34371
34598
|
* `data-navi-route-transition-type="<type>"` next to
|
|
34372
34599
|
* `data-navi-route-transition="forward"|"back"`, and the application's CSS
|
|
34373
|
-
* 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:
|
|
34374
34604
|
*
|
|
34375
34605
|
* :root[data-navi-route-transition-type="spin"][data-navi-route-transition="forward"] {
|
|
34376
|
-
* &::view-transition-new(root)
|
|
34606
|
+
* &::view-transition-new(root),
|
|
34607
|
+
* &::view-transition-new(navi-route-transition) {
|
|
34377
34608
|
* animation-name: my-spin-in;
|
|
34378
34609
|
* }
|
|
34379
34610
|
* }
|
|
@@ -35031,7 +35262,7 @@ const beginTransition = ({
|
|
|
35031
35262
|
// and the movement then plays on the document itself.
|
|
35032
35263
|
const areaElements = document.querySelectorAll(`[${TRANSITION_AREA_ATTRIBUTE}]`);
|
|
35033
35264
|
if (areaElements.length > 1) {
|
|
35034
|
-
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.`);
|
|
35035
35266
|
}
|
|
35036
35267
|
const areaElement = areaElements.length > 0 ? areaElements[0] : null;
|
|
35037
35268
|
// Everything about to be photographed goes deaf to the pointer; the controls
|
|
@@ -35775,8 +36006,9 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
35775
36006
|
* - how to go to a tab. The default REPLACES the current history entry
|
|
35776
36007
|
* rather than pushing one: a swipe is how one browses a page, not a place one
|
|
35777
36008
|
* aimed at, and three swipes back and forth must not bury the way out of the
|
|
35778
|
-
* page under six entries. A tab pressed
|
|
35779
|
-
*
|
|
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.
|
|
35780
36012
|
*
|
|
35781
36013
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`,
|
|
35782
36014
|
* …), which is how one of these is named: a page can hold several — a row of
|
|
@@ -35791,10 +36023,11 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
35791
36023
|
* gesture applies its change on release instead of dragging a picture that does
|
|
35792
36024
|
* not exist.
|
|
35793
36025
|
*
|
|
35794
|
-
* While a travel plays, the rest of the page is taken as a picture
|
|
35795
|
-
*
|
|
35796
|
-
*
|
|
35797
|
-
*
|
|
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.
|
|
35798
36031
|
*/
|
|
35799
36032
|
const RouteTravel = ({
|
|
35800
36033
|
routes: routesProp,
|
|
@@ -36983,9 +37216,9 @@ const writeCover = (edge, element, size) => {
|
|
|
36983
37216
|
|
|
36984
37217
|
/**
|
|
36985
37218
|
* Binds an action to a route: it runs when the route matches, with the params
|
|
36986
|
-
* the effect reads off the address,
|
|
36987
|
-
* Its data, its wait and its failure are read by the
|
|
36988
|
-
* `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`.
|
|
36989
37222
|
*
|
|
36990
37223
|
* A page's CODE is declared the same way — the import is one more thing the
|
|
36991
37224
|
* address asks for, started with the data rather than after it:
|
|
@@ -37029,18 +37262,21 @@ const routeAction = (
|
|
|
37029
37262
|
? routes[0].matchingSignal
|
|
37030
37263
|
: anyMatchingRouteSignal(routes);
|
|
37031
37264
|
const readParams = paramsEffect || (() => true);
|
|
37032
|
-
const
|
|
37033
|
-
|
|
37034
|
-
()
|
|
37035
|
-
|
|
37036
|
-
|
|
37037
|
-
|
|
37038
|
-
|
|
37039
|
-
|
|
37040
|
-
|
|
37041
|
-
|
|
37042
|
-
|
|
37043
|
-
|
|
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
|
+
}
|
|
37044
37280
|
if (prefetch) {
|
|
37045
37281
|
for (const route of routes) {
|
|
37046
37282
|
if (paramsEffect) {
|
|
@@ -38974,10 +39210,11 @@ const flushSyncRendering = (fn) => {
|
|
|
38974
39210
|
* without the content, and the browser builds it in an idle moment after
|
|
38975
39211
|
* load — so by the time anyone clicks, it is usually already there.
|
|
38976
39212
|
*
|
|
38977
|
-
* "while-opened" is the opposite end: content
|
|
38978
|
-
*
|
|
38979
|
-
*
|
|
38980
|
-
*
|
|
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.
|
|
38981
39218
|
*
|
|
38982
39219
|
* On top of whichever value is picked, intent on the anchor warms the content:
|
|
38983
39220
|
* a pointer entering the popup's anchor, or focus landing in it, builds the
|
|
@@ -39011,9 +39248,11 @@ const flushSyncRendering = (fn) => {
|
|
|
39011
39248
|
* click coming after the release, the browser taking the gesture
|
|
39012
39249
|
* (`pointercancel`), or the next press starting, before any of its own
|
|
39013
39250
|
* handlers run: one press at a time holds a content. A keyboard opening builds
|
|
39014
|
-
* at open time. A popup with an `onOpen` is
|
|
39015
|
-
* before the content is built (see
|
|
39016
|
-
* 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.
|
|
39017
39256
|
*/
|
|
39018
39257
|
|
|
39019
39258
|
|
|
@@ -39345,6 +39584,17 @@ const useDisplayedLayoutEffect = (ref, callback, deps) => {
|
|
|
39345
39584
|
decisionDeferredToRef.current = popupMounting;
|
|
39346
39585
|
return;
|
|
39347
39586
|
}
|
|
39587
|
+
if (
|
|
39588
|
+
(el.tagName === "DIALOG" || el.hasAttribute("popover")) &&
|
|
39589
|
+
!isAncestorOpen(el)
|
|
39590
|
+
) {
|
|
39591
|
+
// Itself a closed <dialog> or [popover]: display:none by the UA's own
|
|
39592
|
+
// rules, which the markup says without asking the layout — asked, every
|
|
39593
|
+
// closed popup mounting would force a style recalculation, and a list
|
|
39594
|
+
// whose rows each hold a few mounts hundreds of them. The observer
|
|
39595
|
+
// below fires as for any element the ancestor hides.
|
|
39596
|
+
return;
|
|
39597
|
+
}
|
|
39348
39598
|
if (!isDisplayedDespiteClosedAncestor(el, ancestor)) {
|
|
39349
39599
|
// Ancestor is closed and took this element off screen with it — skip
|
|
39350
39600
|
// now; the observeAncestorOpenState call below will fire once it
|
|
@@ -49610,12 +49860,13 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
49610
49860
|
* does so by being NAMED, which is all the browser needs: any change played as
|
|
49611
49861
|
* a view transition animates it on the same clock as everything else in that
|
|
49612
49862
|
* transition. Inside a `RouteTravel` that means it follows the pages, and the
|
|
49613
|
-
* thumb dragging them, without either of them being told about the other.
|
|
49614
|
-
*
|
|
49615
|
-
*
|
|
49616
|
-
* arrives with its row.
|
|
49617
|
-
*
|
|
49618
|
-
*
|
|
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.
|
|
49619
49870
|
* @param {string} [props.slideContainer] - the id of a `<SlideContainer>` these
|
|
49620
49871
|
* tabs are about: each one says which slide it is (`<Link slide="…">`), the
|
|
49621
49872
|
* container says which one is on screen, and pressing a tab travels there.
|
|
@@ -51447,9 +51698,9 @@ const FixedBar = ({
|
|
|
51447
51698
|
* A page that overflows horizontally is a bug wherever it happens, and on
|
|
51448
51699
|
* Chrome Android it is a catastrophic one: the layout viewport inflates to the
|
|
51449
51700
|
* content and `position: fixed` centering goes with it (see
|
|
51450
|
-
* docs/
|
|
51451
|
-
* `overflow-x: clip`, and it works — at the price of making the
|
|
51452
|
-
* invisible: nothing sticks out anymore, so nothing says a fixed width, a
|
|
51701
|
+
* docs/safe_area.md#a-document-wider-than-the-screen). The remedy there is a
|
|
51702
|
+
* wrapper in `overflow-x: clip`, and it works — at the price of making the
|
|
51703
|
+
* cause invisible: nothing sticks out anymore, so nothing says a fixed width, a
|
|
51453
51704
|
* `min-width` or an unbreakable string is still oversized. This puts the
|
|
51454
51705
|
* signal back, without giving up the net.
|
|
51455
51706
|
*
|
|
@@ -54124,7 +54375,8 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
54124
54375
|
* `command` runs at once — the popup it sits in closes over the run, which
|
|
54125
54376
|
* goes on behind. A failure then has no popup to be read in: `resetOnError`
|
|
54126
54377
|
* puts the button back and the error callout is drawn on what surrounds the
|
|
54127
|
-
* closed popup (see
|
|
54378
|
+
* closed popup (see
|
|
54379
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
54128
54380
|
* @param {boolean} [actionAbortable] The person waiting may give up on this
|
|
54129
54381
|
* button's action: closing the popup the run holds calls it off and goes
|
|
54130
54382
|
* through, instead of being refused. For a run whose answer may never come —
|
|
@@ -63972,7 +64224,7 @@ const css$E = /* css */`
|
|
|
63972
64224
|
the length of the movement, and the picture of the lifted node comes out
|
|
63973
64225
|
empty (Chrome 153, reproduced in a bare page). The price is a page frozen
|
|
63974
64226
|
and unpressable while the movement plays: under a modal wall at the
|
|
63975
|
-
opening, and for
|
|
64227
|
+
opening, and for the popup's --popup-animation-duration at the closing. */
|
|
63976
64228
|
:root[data-navi-popup-lift] {
|
|
63977
64229
|
/* The popup's own pace, published on the root by popup_lift.js because the
|
|
63978
64230
|
::view-transition tree hangs off it and inherits from nowhere else. */
|
|
@@ -64316,16 +64568,21 @@ const css$E = /* css */`
|
|
|
64316
64568
|
* @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
|
|
64317
64569
|
* Where the keyboard goes when this dialog opens — one rung of the ladder in
|
|
64318
64570
|
* `docs/autofocus.md`, which is what to read for the whole of it.
|
|
64319
|
-
* - `true` — the dialog element itself takes the keyboard
|
|
64320
|
-
*
|
|
64321
|
-
*
|
|
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.
|
|
64322
64576
|
* - `"last-resort"` — the dialog takes the keyboard only if it holds nothing
|
|
64323
64577
|
* focusable of its own.
|
|
64324
64578
|
* - `"restore"` — the dialog stays out of the opening focus chain unless it
|
|
64325
64579
|
* held focus when it closed.
|
|
64326
|
-
* - `false` — the dialog
|
|
64327
|
-
*
|
|
64328
|
-
*
|
|
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.
|
|
64329
64586
|
* Wherever the keyboard is a virtual one (a touch device), the surface is
|
|
64330
64587
|
* already what one arrives on: a popup is read before it is reached there, so
|
|
64331
64588
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
@@ -64359,9 +64616,9 @@ const css$E = /* css */`
|
|
|
64359
64616
|
* is "new" or "edit X" depending on the press must know which one it is
|
|
64360
64617
|
* before what it holds is rendered.
|
|
64361
64618
|
* @param {(event: Event) => void} [props.onClose] - Called when the dialog
|
|
64362
|
-
* actually closes — not preventable
|
|
64363
|
-
*
|
|
64364
|
-
*
|
|
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).
|
|
64365
64622
|
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
64366
64623
|
* Keeps the open state in the history entry, so a screen left and come back
|
|
64367
64624
|
* to finds this popup as it was — open, and without an entrance playing: it
|
|
@@ -64379,14 +64636,17 @@ const css$E = /* css */`
|
|
|
64379
64636
|
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
|
|
64380
64637
|
* `children` are built and thrown away (see popup_content_mount.js).
|
|
64381
64638
|
* `"from-first-open"` (the default) builds them on the first open and keeps
|
|
64382
|
-
* them afterwards
|
|
64639
|
+
* them afterwards — and the dialog itself is built then too: until its first
|
|
64640
|
+
* request (an open, a press on what opens it, its signal saying open) it is
|
|
64641
|
+
* a bare `<dialog>` holding its id (see docs/popup_open.md). `"always"` builds them right away, for content something
|
|
64383
64642
|
* depends on while the popup is still closed: a value read off it, fields a
|
|
64384
64643
|
* surrounding form collects on submit, a size measured from outside.
|
|
64385
64644
|
* `"idle"` builds them in a browser idle moment after load — "always" minus
|
|
64386
64645
|
* the cost on the critical render. `"while-opened"` throws them away once
|
|
64387
64646
|
* the popup has finished closing, for content whose fresh state is its
|
|
64388
|
-
* initial state: an
|
|
64389
|
-
*
|
|
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
|
|
64390
64650
|
* entering it, focus landing in it) builds the content ahead of the click;
|
|
64391
64651
|
* under `"while-opened"`, only a press on what opens the popup does (a
|
|
64392
64652
|
* `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
|
|
@@ -64400,11 +64660,122 @@ const Dialog = props => {
|
|
|
64400
64660
|
...props
|
|
64401
64661
|
});
|
|
64402
64662
|
}
|
|
64403
|
-
return jsx(
|
|
64663
|
+
return jsx(DialogStandIn, {
|
|
64404
64664
|
...props
|
|
64405
64665
|
});
|
|
64406
64666
|
};
|
|
64407
64667
|
|
|
64668
|
+
// A dialog nobody has asked for yet stands in as its bare <dialog>. That
|
|
64669
|
+
// element is all that reaches a closed dialog before its first opening: a
|
|
64670
|
+
// command finds it by id (`commandfor`) and asks it to open, a press on what
|
|
64671
|
+
// opens it announces itself on it (navi_open_press), --navi-toggle reads its
|
|
64672
|
+
// aria-expanded. The dialog proper — its open controller, its box, its focus
|
|
64673
|
+
// and pseudo-class wiring — is built at the first request: an open, a press
|
|
64674
|
+
// about to open it, its signal or its `open` saying open. A list whose rows
|
|
64675
|
+
// each hold a few dialogs mounts hundreds of them, and would pay for every one
|
|
64676
|
+
// at every mount, for openings that mostly never come. Built once, the dialog
|
|
64677
|
+
// stays: the next opening finds it there.
|
|
64678
|
+
const DialogStandIn = props => {
|
|
64679
|
+
const {
|
|
64680
|
+
ref,
|
|
64681
|
+
id,
|
|
64682
|
+
signal,
|
|
64683
|
+
value,
|
|
64684
|
+
open
|
|
64685
|
+
} = props;
|
|
64686
|
+
const ownRef = useRef();
|
|
64687
|
+
const dialogRef = ref || ownRef;
|
|
64688
|
+
// Why the dialog proper exists, once it does; never undone.
|
|
64689
|
+
const builtRef = useRef(null);
|
|
64690
|
+
const mountedRef = useRef(false);
|
|
64691
|
+
const isMount = !mountedRef.current;
|
|
64692
|
+
mountedRef.current = true;
|
|
64693
|
+
// Its own reading of the signal: the dialog of one card among many, told
|
|
64694
|
+
// apart by `value`, follows whether IT is the one open rather than every
|
|
64695
|
+
// write the others cause.
|
|
64696
|
+
const openedSignal = useMemo(() => signal ? computed(() => readOpened(signal.value, value)) : null, [signal, value]);
|
|
64697
|
+
const openedBySignal = openedSignal ? openedSignal.value : false;
|
|
64698
|
+
if (builtRef.current === null) {
|
|
64699
|
+
if (isMount && (needsDialogFromMount(props) || open || openedBySignal)) {
|
|
64700
|
+
builtRef.current = "mount";
|
|
64701
|
+
} else if (open || openedBySignal) {
|
|
64702
|
+
// Asked open while this stood in: the opening is happening now, and it
|
|
64703
|
+
// plays like any other (see defaultOpen "interaction").
|
|
64704
|
+
builtRef.current = "interaction";
|
|
64705
|
+
}
|
|
64706
|
+
}
|
|
64707
|
+
const [, setBuildCount] = useState(0);
|
|
64708
|
+
const warmRef = useRef(null);
|
|
64709
|
+
const build = reason => {
|
|
64710
|
+
if (builtRef.current !== null) {
|
|
64711
|
+
return;
|
|
64712
|
+
}
|
|
64713
|
+
builtRef.current = reason;
|
|
64714
|
+
flushSyncRendering(() => {
|
|
64715
|
+
setBuildCount(count => count + 1);
|
|
64716
|
+
});
|
|
64717
|
+
};
|
|
64718
|
+
const builtReason = builtRef.current;
|
|
64719
|
+
if (builtReason !== null) {
|
|
64720
|
+
return jsx(UncontrolledDialog, {
|
|
64721
|
+
...props,
|
|
64722
|
+
ref: dialogRef,
|
|
64723
|
+
defaultOpen: builtReason === "interaction" ? "interaction" : props.defaultOpen
|
|
64724
|
+
});
|
|
64725
|
+
}
|
|
64726
|
+
return jsx("dialog", {
|
|
64727
|
+
ref: dialogRef,
|
|
64728
|
+
id: id,
|
|
64729
|
+
className: "navi_dialog"
|
|
64730
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
64731
|
+
,
|
|
64732
|
+
"navi-hidden": ""
|
|
64733
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
64734
|
+
,
|
|
64735
|
+
"navi-out-of-flow": "",
|
|
64736
|
+
"aria-expanded": "false",
|
|
64737
|
+
"data-testid": props["data-testid"]
|
|
64738
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
64739
|
+
,
|
|
64740
|
+
onnavi_request_open: e => {
|
|
64741
|
+
// Built in this same event, and asked again: the opening is the one
|
|
64742
|
+
// the dialog proper would have had — its source, its anchor, its value.
|
|
64743
|
+
build("request");
|
|
64744
|
+
const opened = dispatchCustomEvent(dialogRef.current, "navi_request_open", e.detail);
|
|
64745
|
+
if (!opened) {
|
|
64746
|
+
e.preventDefault();
|
|
64747
|
+
}
|
|
64748
|
+
}
|
|
64749
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
64750
|
+
,
|
|
64751
|
+
onnavi_open_press: () => {
|
|
64752
|
+
// What the opening will read is asked for at once, as the dialog
|
|
64753
|
+
// proper does (see open_controller.js); the dialog itself two frames
|
|
64754
|
+
// later, so the pressed trigger is painted first — the click that
|
|
64755
|
+
// follows opens it built, or builds it itself when it comes sooner.
|
|
64756
|
+
if (signal) {
|
|
64757
|
+
preloadState(signal, readOpenValue(value));
|
|
64758
|
+
}
|
|
64759
|
+
if (warmRef.current) {
|
|
64760
|
+
return;
|
|
64761
|
+
}
|
|
64762
|
+
warmRef.current = requestFrameAfterNext(() => {
|
|
64763
|
+
warmRef.current = null;
|
|
64764
|
+
build("press");
|
|
64765
|
+
});
|
|
64766
|
+
}
|
|
64767
|
+
});
|
|
64768
|
+
};
|
|
64769
|
+
// What needs the dialog proper from the start: an opening at mount, content
|
|
64770
|
+
// built ahead of any opening, a history entry to read, an anchor whose hover
|
|
64771
|
+
// or focus builds the content ahead of the click.
|
|
64772
|
+
const needsDialogFromMount = props => {
|
|
64773
|
+
if (props.defaultOpen || props.navState || props.anchor !== undefined) {
|
|
64774
|
+
return true;
|
|
64775
|
+
}
|
|
64776
|
+
return props.mount !== undefined && props.mount !== "from-first-open";
|
|
64777
|
+
};
|
|
64778
|
+
|
|
64408
64779
|
// No openController passed: this Dialog is used declaratively (e.g. driven
|
|
64409
64780
|
// by --navi-toggle/--navi-open/--navi-close commands, the `open` prop, or
|
|
64410
64781
|
// `defaultOpen`) rather than owned by a parent component.
|
|
@@ -64944,10 +65315,10 @@ const useDialogProps = props => {
|
|
|
64944
65315
|
}
|
|
64945
65316
|
|
|
64946
65317
|
// Suppressed until committed below — same @starting-style-avoidance
|
|
64947
|
-
// reasoning as popover.jsx's own openEffect (see
|
|
64948
|
-
// though Dialog never needs to measure/flip anything: it
|
|
64949
|
-
// genuinely rendered "closed" frame to transition from, not a
|
|
64950
|
-
// 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".
|
|
64951
65322
|
dialogEl.style.transitionProperty = "none";
|
|
64952
65323
|
if (backdropEl) {
|
|
64953
65324
|
disarmBackdropHideRef.current?.();
|
|
@@ -66024,10 +66395,11 @@ const css$D = /* css */`
|
|
|
66024
66395
|
* @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
|
|
66025
66396
|
* Where the keyboard goes when this popover opens — one rung of the ladder in
|
|
66026
66397
|
* `docs/autofocus.md`, which is what to read for the whole of it.
|
|
66027
|
-
* - `true` — the popover element itself takes the keyboard
|
|
66028
|
-
*
|
|
66029
|
-
*
|
|
66030
|
-
* 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.
|
|
66031
66403
|
* - `"last-resort"` — the popover takes the keyboard only if it holds nothing
|
|
66032
66404
|
* focusable of its own.
|
|
66033
66405
|
* - `"restore"` — the popover stays out of the opening focus chain unless it
|
|
@@ -66068,9 +66440,9 @@ const css$D = /* css */`
|
|
|
66068
66440
|
* is "new" or "edit X" depending on the press must know which one it is
|
|
66069
66441
|
* before what it holds is rendered.
|
|
66070
66442
|
* @param {(event: Event) => void} [props.onClose] - Called when the popover
|
|
66071
|
-
* actually closes — not preventable
|
|
66072
|
-
*
|
|
66073
|
-
*
|
|
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).
|
|
66074
66446
|
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
66075
66447
|
* Keeps the open state in the history entry, so a screen left and come back
|
|
66076
66448
|
* to finds this popup as it was — open, and without an entrance playing: it
|
|
@@ -66094,8 +66466,9 @@ const css$D = /* css */`
|
|
|
66094
66466
|
* `"idle"` builds them in a browser idle moment after load — "always" minus
|
|
66095
66467
|
* the cost on the critical render. `"while-opened"` throws them away once
|
|
66096
66468
|
* the popup has finished closing, for content whose fresh state is its
|
|
66097
|
-
* initial state: an
|
|
66098
|
-
*
|
|
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
|
|
66099
66472
|
* entering it, focus landing in it) builds the content ahead of the click;
|
|
66100
66473
|
* under `"while-opened"`, only a press on what opens the popup does (a
|
|
66101
66474
|
* `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
|
|
@@ -66520,7 +66893,7 @@ const usePopoverProps = props => {
|
|
|
66520
66893
|
});
|
|
66521
66894
|
|
|
66522
66895
|
// Suppressed until the popover is actually measured/positioned below —
|
|
66523
|
-
// see
|
|
66896
|
+
// see popup_css.js's top comment for why @starting-style can't drive the
|
|
66524
66897
|
// opening transition (it needs the popover's actual resting position
|
|
66525
66898
|
// already in place, which requires a layout box that only exists once
|
|
66526
66899
|
// shown).
|
|
@@ -66868,7 +67241,7 @@ const usePopoverProps = props => {
|
|
|
66868
67241
|
// frame set up above as a real rendered state (the reflow), re-enables
|
|
66869
67242
|
// transitions, flips aria-expanded="true" (only then does the CSS
|
|
66870
67243
|
// transition play, from that just-committed frame to the open one,
|
|
66871
|
-
// with no @starting-style involved at all — see
|
|
67244
|
+
// with no @starting-style involved at all — see popup_css.js's top
|
|
66872
67245
|
// comment), suppresses pointer events until it settles, and transfers
|
|
66873
67246
|
// focus in. Inlined rather than a standalone function since it only
|
|
66874
67247
|
// has this one call site.
|
|
@@ -67355,8 +67728,9 @@ const css$C = /* css */`@layer navi {
|
|
|
67355
67728
|
* `"idle"` builds them in a browser idle moment after load — `"always"`
|
|
67356
67729
|
* minus the cost on the critical render.
|
|
67357
67730
|
* `"while-opened"` throws them away once the popup has finished closing, for
|
|
67358
|
-
* content whose fresh state is its initial state: an
|
|
67359
|
-
*
|
|
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).
|
|
67360
67734
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
67361
67735
|
*/
|
|
67362
67736
|
const Popup = props => {
|
|
@@ -67655,8 +68029,9 @@ const PickerCustom = props => {
|
|
|
67655
68029
|
open,
|
|
67656
68030
|
defaultOpen,
|
|
67657
68031
|
// What Escape means for this picker. "cancel" (the default) puts back the
|
|
67658
|
-
// value the picker had at open and, for a dialog
|
|
67659
|
-
// 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).
|
|
67660
68035
|
// "close" makes Escape say the same thing as clicking outside: keep what
|
|
67661
68036
|
// was chosen, close the popup.
|
|
67662
68037
|
escapeEffect = "cancel",
|
|
@@ -68310,9 +68685,10 @@ const PickerContentInsidePopup = props => {
|
|
|
68310
68685
|
// itself, and this one does not. Popover ignores it, same as Dialog ignores
|
|
68311
68686
|
// marginWithAnchor.
|
|
68312
68687
|
dockedOnSmallTouchScreen,
|
|
68313
|
-
// Same again: the dialog takes the trigger's box as a floor (
|
|
68314
|
-
//
|
|
68315
|
-
//
|
|
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.
|
|
68316
68692
|
dialogSizeFromAnchor,
|
|
68317
68693
|
// The caller's word on the popup's own box. Written on the popup element,
|
|
68318
68694
|
// where Popover/Dialog map them to their own vars, rather than as vars on
|
|
@@ -76033,10 +76409,10 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
76033
76409
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
76034
76410
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
76035
76411
|
* placeholder text: `fallback="Select skills…"`. Plain text is the right
|
|
76036
|
-
* choice there: it reads at the picker's size, in the
|
|
76037
|
-
*
|
|
76038
|
-
* transparent <Badge> matches the slot to the pixel instead, at the
|
|
76039
|
-
* badge-sized text. See docs/
|
|
76412
|
+
* choice there: it reads at the picker's size, in the normal text color (a
|
|
76413
|
+
* caller's `ui` is never greyed as a placeholder), and the box stays the same
|
|
76414
|
+
* height. A transparent <Badge> matches the slot to the pixel instead, at the
|
|
76415
|
+
* price of badge-sized text. See docs/typography.md#badgelist.
|
|
76040
76416
|
* @param {boolean} [shrinkWrap]
|
|
76041
76417
|
* Narrows the list down to its widest row so the last row isn't ragged.
|
|
76042
76418
|
* Defaults to true inside a <Picker> — the trigger draws a border around the
|