@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/dev/jsenv_navi.js
CHANGED
|
@@ -426,6 +426,22 @@ const whenRenderingResumes = (callback) => {
|
|
|
426
426
|
renderingHold.waiting.push(callback);
|
|
427
427
|
};
|
|
428
428
|
|
|
429
|
+
// After the render the change being applied has queued, for what depends on
|
|
430
|
+
// the page that change puts on screen — an offset given back to a page is
|
|
431
|
+
// clamped to whatever page the document holds when it is written. Under a hold
|
|
432
|
+
// that render runs when the hold is released, and this right after it.
|
|
433
|
+
// Otherwise the render is Preact's own, queued in a microtask, and every
|
|
434
|
+
// microtask has run by the next frame — before it paints.
|
|
435
|
+
const whenPageRendered = (callback) => {
|
|
436
|
+
if (renderingHold) {
|
|
437
|
+
renderingHold.waiting.push(callback);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
requestAnimationFrame(() => {
|
|
441
|
+
callback();
|
|
442
|
+
});
|
|
443
|
+
};
|
|
444
|
+
|
|
429
445
|
// The hold a navigation takes on its way in — from before its first write,
|
|
430
446
|
// because by the time a route announces that it matches, Preact has already
|
|
431
447
|
// been told and the render is queued; a hold taken then is a hold taken too
|
|
@@ -800,7 +816,10 @@ const writeTarget = (element) => {
|
|
|
800
816
|
silencedElement.id = addressTargetId;
|
|
801
817
|
}
|
|
802
818
|
window.history.replaceState(historyState, null, address);
|
|
803
|
-
|
|
819
|
+
// Only a state the entry had is put back: an entry nobody wrote one on
|
|
820
|
+
// (via_history.js never does) has nothing to lose, and updateCurrentEntry
|
|
821
|
+
// cannot say "none" — `state` is a required member, and undefined throws.
|
|
822
|
+
if (entry && navigation.currentEntry && entryState !== undefined) {
|
|
804
823
|
navigation.updateCurrentEntry({ state: entryState });
|
|
805
824
|
}
|
|
806
825
|
targetWriteInProgress = false;
|
|
@@ -3675,6 +3694,8 @@ const actionWeakMap = new WeakMap();
|
|
|
3675
3694
|
* than a first load (data_states.md). Called at the start of every run whose
|
|
3676
3695
|
* value is undefined; the answer replaces it as usual, and `undefined` means
|
|
3677
3696
|
* "nothing known". A resource `GET` uses it to draw the row its store holds.
|
|
3697
|
+
* An action declaring it is asked for it by a binding moving to new params,
|
|
3698
|
+
* rather than handed the previous params' answer (see inheritData).
|
|
3678
3699
|
* @param {{ ms?: number, max?: number }} [rootOptions.keep] - how long an
|
|
3679
3700
|
* answer stays good once it has landed. Without it an answer lives exactly as
|
|
3680
3701
|
* long as something references it: the screen that asked goes away, and
|
|
@@ -4342,7 +4363,14 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4342
4363
|
if (rejected) {
|
|
4343
4364
|
return failRun(rejectedValue);
|
|
4344
4365
|
}
|
|
4345
|
-
|
|
4366
|
+
// Turning the answer into a value can throw as well — a resource
|
|
4367
|
+
// write answering nothing to upsert. That fails the run, as the
|
|
4368
|
+
// catch below does when the callback answered synchronously.
|
|
4369
|
+
try {
|
|
4370
|
+
return onRunEnd();
|
|
4371
|
+
} catch (e) {
|
|
4372
|
+
return failRun(e);
|
|
4373
|
+
}
|
|
4346
4374
|
});
|
|
4347
4375
|
} catch (e) {
|
|
4348
4376
|
return failRun(e);
|
|
@@ -4385,6 +4413,7 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4385
4413
|
|
|
4386
4414
|
const privateProperties = {
|
|
4387
4415
|
valueInitial,
|
|
4416
|
+
provisionalValue,
|
|
4388
4417
|
|
|
4389
4418
|
performRun,
|
|
4390
4419
|
performReset,
|
|
@@ -4416,7 +4445,8 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4416
4445
|
* This enables live updates - for example, performing an HTTP GET request every time
|
|
4417
4446
|
* a list of filters changes, providing real-time results without user interaction.
|
|
4418
4447
|
* @param {boolean} options.inheritData - When false, each new target action starts fresh with no inherited state.
|
|
4419
|
-
* By default (true), the proxy carries over the previous target's value and error into the new action
|
|
4448
|
+
* By default (true), the proxy carries over the previous target's value and error into the new action —
|
|
4449
|
+
* unless the action declares a `provisionalValue`, which then says what the new one holds.
|
|
4420
4450
|
* This keeps the facade in sync with the latest known data: `action.dataSignal.value` only changes when a
|
|
4421
4451
|
* new action completes, not when it starts loading. Code that needs to distinguish loading state can still
|
|
4422
4452
|
* check `action.runningState`, while code that just reads `action.data` always sees the most recent
|
|
@@ -4463,8 +4493,14 @@ const createActionProxyFromSignal = (
|
|
|
4463
4493
|
let currentActionPrivateProperties = getActionPrivateProperties(action);
|
|
4464
4494
|
let actionTargetPreviousWeakRef = null;
|
|
4465
4495
|
|
|
4496
|
+
// An action saying for itself what it holds for params it has not answered
|
|
4497
|
+
// (provisionalValue: a resource GET draws the row its store holds) is not
|
|
4498
|
+
// handed the previous params' answer — an answer about another row. What it
|
|
4499
|
+
// does not know stays unknown: a skeleton, never a different row.
|
|
4500
|
+
const inheritsAnswer =
|
|
4501
|
+
inheritData && !getActionPrivateProperties(action).provisionalValue;
|
|
4466
4502
|
const createTarget = (params) => {
|
|
4467
|
-
if (
|
|
4503
|
+
if (inheritsAnswer) {
|
|
4468
4504
|
const previousActionTarget = actionTargetPreviousWeakRef?.deref();
|
|
4469
4505
|
const previousTarget = previousActionTarget || action;
|
|
4470
4506
|
return action.bindParams(params, {
|
|
@@ -4577,6 +4613,12 @@ const createActionProxyFromSignal = (
|
|
|
4577
4613
|
return signalProxy;
|
|
4578
4614
|
};
|
|
4579
4615
|
|
|
4616
|
+
// Params bound onto this binding make the action bound to both — these over
|
|
4617
|
+
// the ones it follows, as an instance's go over its action's — following the
|
|
4618
|
+
// signals of both: what a control does with the action it is given, and
|
|
4619
|
+
// actionRunEffect. Kept per params like the action's own bindings, since a
|
|
4620
|
+
// control binds its action again at every render.
|
|
4621
|
+
const bindingOnProxyWeakMap = createJsValueWeakMap();
|
|
4580
4622
|
Object.assign(actionProxy, {
|
|
4581
4623
|
isAction: true,
|
|
4582
4624
|
isProxy: true,
|
|
@@ -4601,14 +4643,27 @@ const createActionProxyFromSignal = (
|
|
|
4601
4643
|
});
|
|
4602
4644
|
return currentAction;
|
|
4603
4645
|
},
|
|
4604
|
-
bindParams: () => {
|
|
4605
|
-
|
|
4606
|
-
|
|
4646
|
+
bindParams: (newParamsOrSignal, options) => {
|
|
4647
|
+
const existingBinding = bindingOnProxyWeakMap.get(newParamsOrSignal);
|
|
4648
|
+
if (existingBinding) {
|
|
4649
|
+
return existingBinding;
|
|
4650
|
+
}
|
|
4651
|
+
const binding = action.bindParams(
|
|
4652
|
+
computed(() =>
|
|
4653
|
+
mergeActionParams(
|
|
4654
|
+
paramsSignal.value,
|
|
4655
|
+
readParamsHandedToBind(newParamsOrSignal),
|
|
4656
|
+
),
|
|
4657
|
+
),
|
|
4658
|
+
options,
|
|
4607
4659
|
);
|
|
4660
|
+
bindingOnProxyWeakMap.set(newParamsOrSignal, binding);
|
|
4661
|
+
return binding;
|
|
4608
4662
|
},
|
|
4609
4663
|
replaceParams: null, // Will be set below
|
|
4610
4664
|
toString: () => callSourceSignal.peek(), // peek: see the action's toString
|
|
4611
4665
|
meta: action.meta,
|
|
4666
|
+
debug: action.debug,
|
|
4612
4667
|
|
|
4613
4668
|
paramsSignal: proxyParamsSignal,
|
|
4614
4669
|
paramsSettlingSignal,
|
|
@@ -4751,6 +4806,29 @@ const generateActionCallSource = (name, params) => {
|
|
|
4751
4806
|
return `${name}${argsString}`;
|
|
4752
4807
|
};
|
|
4753
4808
|
|
|
4809
|
+
// What params handed to bindParams say at this moment, read as a binding reads
|
|
4810
|
+
// them (see _bindParams): a signal is its value, and so is each signal a plain
|
|
4811
|
+
// object holds at its first level. Read inside a computed, it follows them.
|
|
4812
|
+
const readParamsHandedToBind = (newParamsOrSignal) => {
|
|
4813
|
+
if (isSignal(newParamsOrSignal)) {
|
|
4814
|
+
return newParamsOrSignal.value;
|
|
4815
|
+
}
|
|
4816
|
+
if (!isPlainObject(newParamsOrSignal)) {
|
|
4817
|
+
return newParamsOrSignal;
|
|
4818
|
+
}
|
|
4819
|
+
const params = {};
|
|
4820
|
+
for (const key of Object.keys(newParamsOrSignal)) {
|
|
4821
|
+
const value = newParamsOrSignal[key];
|
|
4822
|
+
const valueSignal = isSignal(value)
|
|
4823
|
+
? value
|
|
4824
|
+
: value
|
|
4825
|
+
? value[SYMBOL_OBJECT_SIGNAL]
|
|
4826
|
+
: undefined;
|
|
4827
|
+
params[key] = valueSignal ? valueSignal.value : value;
|
|
4828
|
+
}
|
|
4829
|
+
return params;
|
|
4830
|
+
};
|
|
4831
|
+
|
|
4754
4832
|
const isPlainObject = (obj) => {
|
|
4755
4833
|
if (typeof obj !== "object" || obj === null) {
|
|
4756
4834
|
return false;
|
|
@@ -4863,9 +4941,16 @@ const actionRunEffect = (
|
|
|
4863
4941
|
}
|
|
4864
4942
|
return params;
|
|
4865
4943
|
});
|
|
4944
|
+
// Whether the getter asks for a run is read off what it returned, never off
|
|
4945
|
+
// the params of the instance it leads to: an action handed here holding
|
|
4946
|
+
// params of its own (an instance, a binding) keeps them when the getter
|
|
4947
|
+
// returns nothing — and nothing must still run nothing.
|
|
4948
|
+
let asking = Boolean(actionParamsSignal.peek());
|
|
4866
4949
|
const actionRunnedByThisEffect = action.bindParams(actionParamsSignal, {
|
|
4867
4950
|
debounce,
|
|
4868
4951
|
onChange: (actionTarget, actionTargetPrevious, { explicitRunIntent }) => {
|
|
4952
|
+
const askingBefore = asking;
|
|
4953
|
+
asking = Boolean(actionParamsSignal.peek());
|
|
4869
4954
|
if (explicitRunIntent) {
|
|
4870
4955
|
// The caller already issued an explicit run/rerun/prerun/reset/abort —
|
|
4871
4956
|
// don't attempt to also auto-run from the params change to avoid double-runs.
|
|
@@ -4876,7 +4961,7 @@ const actionRunEffect = (
|
|
|
4876
4961
|
}
|
|
4877
4962
|
if (!actionTargetPrevious && actionTarget) {
|
|
4878
4963
|
// first run
|
|
4879
|
-
if (!
|
|
4964
|
+
if (!asking) {
|
|
4880
4965
|
// falsy params, don't run
|
|
4881
4966
|
return;
|
|
4882
4967
|
}
|
|
@@ -4892,12 +4977,12 @@ const actionRunEffect = (
|
|
|
4892
4977
|
actionTarget
|
|
4893
4978
|
) {
|
|
4894
4979
|
// params changed
|
|
4895
|
-
if (!
|
|
4980
|
+
if (!asking) {
|
|
4896
4981
|
// falsy params, don't run
|
|
4897
4982
|
actionTargetPrevious.abort("abortOnFalsyParams");
|
|
4898
4983
|
return;
|
|
4899
4984
|
}
|
|
4900
|
-
if (!
|
|
4985
|
+
if (!askingBefore) {
|
|
4901
4986
|
// coming from falsy-params state: action may already be cached, avoid unnecessary rerun
|
|
4902
4987
|
runUnwatched(() =>
|
|
4903
4988
|
actionTarget.run({ reason: "params restored from falsy state" }),
|
|
@@ -4909,7 +4994,7 @@ const actionRunEffect = (
|
|
|
4909
4994
|
},
|
|
4910
4995
|
...options,
|
|
4911
4996
|
});
|
|
4912
|
-
if (
|
|
4997
|
+
if (asking) {
|
|
4913
4998
|
runUnwatched(() =>
|
|
4914
4999
|
actionRunnedByThisEffect.run({ reason: "initial truthy params" }),
|
|
4915
5000
|
);
|
|
@@ -6981,6 +7066,28 @@ let navDepthMax = 0;
|
|
|
6981
7066
|
|
|
6982
7067
|
const getNavDepth = () => navDepth;
|
|
6983
7068
|
|
|
7069
|
+
// Where an entry's state says it stands, undefined when it says nothing.
|
|
7070
|
+
const readNavDepthInState = (state) => {
|
|
7071
|
+
if (state && typeof state[NAV_DEPTH_STATE_KEY] === "number") {
|
|
7072
|
+
return state[NAV_DEPTH_STATE_KEY];
|
|
7073
|
+
}
|
|
7074
|
+
return undefined;
|
|
7075
|
+
};
|
|
7076
|
+
|
|
7077
|
+
// The state of the entry a document is loaded into, carrying its depth. One
|
|
7078
|
+
// that says a depth was written by this document (a reload in the middle of
|
|
7079
|
+
// the stack) and keeps it. One that says none was not: a url opened cold, the
|
|
7080
|
+
// first of the stack — or, rarely, an entry the browser made for a fragment,
|
|
7081
|
+
// reloaded, which 0 under-reports as above. Written into the entry by the
|
|
7082
|
+
// integrations, because a back that returns there reads where it stands from
|
|
7083
|
+
// the entry alone.
|
|
7084
|
+
const stateWithNavDepthOnLoad = (state) => {
|
|
7085
|
+
if (readNavDepthInState(state) !== undefined) {
|
|
7086
|
+
return state;
|
|
7087
|
+
}
|
|
7088
|
+
return { ...state, [NAV_DEPTH_STATE_KEY]: 0 };
|
|
7089
|
+
};
|
|
7090
|
+
|
|
6984
7091
|
const applyNavigationToNavDepth = (navigationType, state) => {
|
|
6985
7092
|
if (navigationType === "push") {
|
|
6986
7093
|
navDepth++;
|
|
@@ -6988,10 +7095,7 @@ const applyNavigationToNavDepth = (navigationType, state) => {
|
|
|
6988
7095
|
navDepthMax = navDepth;
|
|
6989
7096
|
} else if (navigationType === "replace") ; else {
|
|
6990
7097
|
// load, reload, traverse: the entry itself says where it stands.
|
|
6991
|
-
const depthInState =
|
|
6992
|
-
state && typeof state[NAV_DEPTH_STATE_KEY] === "number"
|
|
6993
|
-
? state[NAV_DEPTH_STATE_KEY]
|
|
6994
|
-
: undefined;
|
|
7098
|
+
const depthInState = readNavDepthInState(state);
|
|
6995
7099
|
if (depthInState !== undefined) {
|
|
6996
7100
|
navDepth = depthInState;
|
|
6997
7101
|
if (navDepth > navDepthMax) {
|
|
@@ -10524,6 +10628,18 @@ const preloadUrl = (url) => {
|
|
|
10524
10628
|
}
|
|
10525
10629
|
};
|
|
10526
10630
|
|
|
10631
|
+
// What a reload asks of each action bound to a route (see routeAction): the
|
|
10632
|
+
// action to run again, or nothing when its params ask nothing.
|
|
10633
|
+
const routeReloadMap = new WeakMap();
|
|
10634
|
+
const registerRouteReload = (route, reload) => {
|
|
10635
|
+
let reloadSet = routeReloadMap.get(route);
|
|
10636
|
+
if (!reloadSet) {
|
|
10637
|
+
reloadSet = new Set();
|
|
10638
|
+
routeReloadMap.set(route, reloadSet);
|
|
10639
|
+
}
|
|
10640
|
+
reloadSet.add(reload);
|
|
10641
|
+
};
|
|
10642
|
+
|
|
10527
10643
|
const routeStatePreloadMap = new WeakMap();
|
|
10528
10644
|
const registerRouteStatePreload = (route, preload) => {
|
|
10529
10645
|
let preloadSet = routeStatePreloadMap.get(route);
|
|
@@ -10613,6 +10729,7 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
10613
10729
|
const updateRoutes = (
|
|
10614
10730
|
url,
|
|
10615
10731
|
{
|
|
10732
|
+
navigationType,
|
|
10616
10733
|
isVisited = () => false,
|
|
10617
10734
|
// state
|
|
10618
10735
|
} = {},
|
|
@@ -10912,6 +11029,27 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
10912
11029
|
}
|
|
10913
11030
|
}
|
|
10914
11031
|
|
|
11032
|
+
// A route's actions run again when their params change (see routeAction),
|
|
11033
|
+
// and a reload changes none: it asks again for the page one is on. So the
|
|
11034
|
+
// actions of every matching route are handed back, to be rerun — except
|
|
11035
|
+
// those whose params ask nothing.
|
|
11036
|
+
if (navigationType === "reload") {
|
|
11037
|
+
const reloadSet = new Set();
|
|
11038
|
+
for (const route of returnValue.matchingRouteSet) {
|
|
11039
|
+
const routeReloadSet = routeReloadMap.get(route);
|
|
11040
|
+
if (!routeReloadSet) {
|
|
11041
|
+
continue;
|
|
11042
|
+
}
|
|
11043
|
+
for (const reload of routeReloadSet) {
|
|
11044
|
+
const actionToRerun = reload();
|
|
11045
|
+
if (actionToRerun) {
|
|
11046
|
+
reloadSet.add(actionToRerun);
|
|
11047
|
+
}
|
|
11048
|
+
}
|
|
11049
|
+
}
|
|
11050
|
+
returnValue.reloadSet = reloadSet;
|
|
11051
|
+
}
|
|
11052
|
+
|
|
10915
11053
|
return returnValue;
|
|
10916
11054
|
};
|
|
10917
11055
|
|
|
@@ -11318,11 +11456,18 @@ const installScrollRestoration = () => {
|
|
|
11318
11456
|
}
|
|
11319
11457
|
};
|
|
11320
11458
|
|
|
11459
|
+
// Where the document lands is the last arrival's to say. A return waits for its
|
|
11460
|
+
// page (see restoreScrollPositionOnReturn), and an arrival decided meanwhile — a
|
|
11461
|
+
// page that sends the reader elsewhere as it mounts — must not be scrolled to
|
|
11462
|
+
// where the return was going.
|
|
11463
|
+
let arrivalCount = 0;
|
|
11464
|
+
|
|
11321
11465
|
// Nothing to put back is not the same as putting back the top: a page arrived
|
|
11322
11466
|
// at for the first time is startAtTop's business, and this must not step on it.
|
|
11323
11467
|
// Whether there was anything, for a caller who has an answer of its own for the
|
|
11324
11468
|
// page that has never been read.
|
|
11325
11469
|
const restoreScrollPosition = (url) => {
|
|
11470
|
+
arrivalCount++;
|
|
11326
11471
|
const position = positionByUrl.get(new URL(url, window.location.href).href);
|
|
11327
11472
|
if (!position) {
|
|
11328
11473
|
return false;
|
|
@@ -11336,6 +11481,28 @@ const restoreScrollPosition = (url) => {
|
|
|
11336
11481
|
return true;
|
|
11337
11482
|
};
|
|
11338
11483
|
|
|
11484
|
+
// A traversal back to `url`. The offset is written once the page returned to is
|
|
11485
|
+
// rendered: written sooner, the document still holds the page being left and
|
|
11486
|
+
// clamps it to that page's height. It is read at once all the same — a page
|
|
11487
|
+
// left taller than the one arriving is clamped by the render itself, and that
|
|
11488
|
+
// clamp is a scroll recorded under the url already returned to.
|
|
11489
|
+
const restoreScrollPositionOnReturn = (url) => {
|
|
11490
|
+
const arrival = ++arrivalCount;
|
|
11491
|
+
const position = positionByUrl.get(new URL(url, window.location.href).href);
|
|
11492
|
+
if (!position) {
|
|
11493
|
+
return;
|
|
11494
|
+
}
|
|
11495
|
+
whenPageRendered(() => {
|
|
11496
|
+
if (arrival !== arrivalCount) {
|
|
11497
|
+
return;
|
|
11498
|
+
}
|
|
11499
|
+
if (documentHoldCount) {
|
|
11500
|
+
return;
|
|
11501
|
+
}
|
|
11502
|
+
scrollTo(position);
|
|
11503
|
+
});
|
|
11504
|
+
};
|
|
11505
|
+
|
|
11339
11506
|
// A page one arrives at for the first time starts at its top. Only a document
|
|
11340
11507
|
// navigation does that on its own: a pushState creates its entry with whatever
|
|
11341
11508
|
// scroll happened to be there, so without this the page opens at the offset of
|
|
@@ -11361,6 +11528,7 @@ const startAtTop = (url, { from } = {}) => {
|
|
|
11361
11528
|
if (!isArrival(url, { from })) {
|
|
11362
11529
|
return;
|
|
11363
11530
|
}
|
|
11531
|
+
arrivalCount++;
|
|
11364
11532
|
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
11365
11533
|
};
|
|
11366
11534
|
const isArrival = (url, { from }) => {
|
|
@@ -11831,12 +11999,12 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11831
11999
|
isVisited,
|
|
11832
12000
|
state,
|
|
11833
12001
|
});
|
|
11834
|
-
// Where the document lands, said by what kind of arrival this is.
|
|
11835
|
-
//
|
|
11836
|
-
//
|
|
11837
|
-
//
|
|
11838
|
-
//
|
|
11839
|
-
// it is
|
|
12002
|
+
// Where the document lands, said by what kind of arrival this is. Never
|
|
12003
|
+
// before the picture of a transition is taken, which would be of a page
|
|
12004
|
+
// at its top (see rendering_hold.js), and after the history has been
|
|
12005
|
+
// written, so the entry being left keeps the offset it is at. A return
|
|
12006
|
+
// also waits for the page it returns to: an offset is clamped to whatever
|
|
12007
|
+
// page the document holds when it is written.
|
|
11840
12008
|
//
|
|
11841
12009
|
// A replace gets neither: it is the same place said differently — a param
|
|
11842
12010
|
// settling, a state written — and moving the reader for it would throw
|
|
@@ -11847,7 +12015,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11847
12015
|
if (navigationType === "push") {
|
|
11848
12016
|
whenRenderingResumes(() => startAtTop(url, { from: urlLeft }));
|
|
11849
12017
|
} else if (navigationType === "traverse") {
|
|
11850
|
-
|
|
12018
|
+
restoreScrollPositionOnReturn(url);
|
|
11851
12019
|
}
|
|
11852
12020
|
executeWithCleanup(
|
|
11853
12021
|
() => allResult,
|
|
@@ -12082,11 +12250,12 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12082
12250
|
const init = () => {
|
|
12083
12251
|
const url = window.location.href;
|
|
12084
12252
|
const stateOnEntry = window.history.state;
|
|
12085
|
-
const state = dropGeneratedIdKeys(stateOnEntry);
|
|
12253
|
+
const state = stateWithNavDepthOnLoad(dropGeneratedIdKeys(stateOnEntry));
|
|
12086
12254
|
if (state !== stateOnEntry) {
|
|
12087
|
-
// The entry itself has to
|
|
12088
|
-
// getDocumentState() reads the entry back,
|
|
12089
|
-
//
|
|
12255
|
+
// The entry itself has to carry it, not just the document state:
|
|
12256
|
+
// getDocumentState() reads the entry back, every state write copies what
|
|
12257
|
+
// it finds there onto the next one, and a back returning here reads its
|
|
12258
|
+
// depth from it.
|
|
12090
12259
|
writeHistoryEntry("replace", state, url);
|
|
12091
12260
|
}
|
|
12092
12261
|
handleRoutingTask(url, {
|
|
@@ -12256,7 +12425,6 @@ const applyRouting = (
|
|
|
12256
12425
|
url,
|
|
12257
12426
|
{
|
|
12258
12427
|
globalAbortSignal,
|
|
12259
|
-
abortSignal,
|
|
12260
12428
|
// state
|
|
12261
12429
|
navigationType,
|
|
12262
12430
|
isVisited,
|
|
@@ -12268,45 +12436,36 @@ const applyRouting = (
|
|
|
12268
12436
|
// likely because code does not uses routing at all
|
|
12269
12437
|
return {};
|
|
12270
12438
|
}
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
routeLoadRequestedMap,
|
|
12276
|
-
activeRouteSet,
|
|
12277
|
-
} = updateRoutes(url, {
|
|
12439
|
+
// The routes' actions follow the address by themselves (see routeAction):
|
|
12440
|
+
// what a navigation leaves to the routing is a reload, the one that asks
|
|
12441
|
+
// them again with the address unchanged.
|
|
12442
|
+
const { reloadSet } = updateRoutes(url, {
|
|
12278
12443
|
navigationType,
|
|
12279
12444
|
isVisited,
|
|
12280
12445
|
// state,
|
|
12281
12446
|
});
|
|
12282
|
-
if (
|
|
12283
|
-
(!loadSet || loadSet.size === 0) &&
|
|
12284
|
-
(!reloadSet || reloadSet.size === 0)
|
|
12285
|
-
) {
|
|
12447
|
+
if (!reloadSet || reloadSet.size === 0) {
|
|
12286
12448
|
return {
|
|
12287
12449
|
allResult: undefined,
|
|
12288
12450
|
requestedResult: undefined,
|
|
12289
|
-
activeRouteSet: new Set(),
|
|
12290
12451
|
};
|
|
12291
12452
|
}
|
|
12292
12453
|
const updateActionsResult = updateActions({
|
|
12293
12454
|
globalAbortSignal,
|
|
12294
|
-
|
|
12295
|
-
|
|
12455
|
+
// Not the navigation's own signal: the next navigation aborts that one, and
|
|
12456
|
+
// an action whose route still matches there would be left aborted — its
|
|
12457
|
+
// params do not change, so nothing would run it again.
|
|
12458
|
+
abortSignal: new AbortController().signal,
|
|
12296
12459
|
rerunSet: reloadSet,
|
|
12297
|
-
abortSignalMap,
|
|
12298
12460
|
reason,
|
|
12299
|
-
isReplace: navigationType === "replace",
|
|
12300
12461
|
});
|
|
12301
12462
|
const { allResult, runningActionSet } = updateActionsResult;
|
|
12302
12463
|
const pendingTaskNameArray = [];
|
|
12303
|
-
for (const
|
|
12304
|
-
|
|
12305
|
-
pendingTaskNameArray.push(`${route.relativeUrl} -> ${routeAction.name}`);
|
|
12306
|
-
}
|
|
12464
|
+
for (const runningAction of runningActionSet) {
|
|
12465
|
+
pendingTaskNameArray.push(runningAction.name);
|
|
12307
12466
|
}
|
|
12308
12467
|
routingWhile(() => allResult, pendingTaskNameArray);
|
|
12309
|
-
return
|
|
12468
|
+
return updateActionsResult;
|
|
12310
12469
|
};
|
|
12311
12470
|
|
|
12312
12471
|
const setupBrowserIntegration =
|
|
@@ -12388,6 +12547,12 @@ const stopLoad = (reason = "stopLoad() called") => {
|
|
|
12388
12547
|
browserIntegration.stop(reason);
|
|
12389
12548
|
}
|
|
12390
12549
|
};
|
|
12550
|
+
/**
|
|
12551
|
+
* Asks again for the page one is on, without reloading the document: the
|
|
12552
|
+
* actions of the routes matching the url run again (see routeAction), one that
|
|
12553
|
+
* failed included — what a "Retry" under a page that could not load calls.
|
|
12554
|
+
* The address, the history and the scroll stay as they are.
|
|
12555
|
+
*/
|
|
12391
12556
|
const reload = browserIntegration.reload;
|
|
12392
12557
|
/**
|
|
12393
12558
|
* Go back to the screen this document came from.
|
|
@@ -12410,11 +12575,13 @@ const reload = browserIntegration.reload;
|
|
|
12410
12575
|
* way a screen closed over a url keeps what was written to the url while it
|
|
12411
12576
|
* was open (see useNavState's leave()).
|
|
12412
12577
|
* @returns {Promise<boolean>|undefined}
|
|
12413
|
-
* When there is something to go back to: a promise resolved
|
|
12414
|
-
* has landed and been applied
|
|
12415
|
-
* where it landed
|
|
12416
|
-
* nowhere
|
|
12417
|
-
* the
|
|
12578
|
+
* When there is something to go back to: a promise resolved with `true`
|
|
12579
|
+
* once the back has landed and been applied — the document url and state
|
|
12580
|
+
* then say where it landed. A back preempted by another navigation, which
|
|
12581
|
+
* lands nowhere, resolves `false` only under the Navigation API integration
|
|
12582
|
+
* (see USE_NAVIGATION_API, off today): the History API does not tell it
|
|
12583
|
+
* apart. `undefined` otherwise — nothing happened, or the fallback took the
|
|
12584
|
+
* current entry's place.
|
|
12418
12585
|
*/
|
|
12419
12586
|
const navBack = browserIntegration.navBack;
|
|
12420
12587
|
const navForward = browserIntegration.navForward;
|
|
@@ -13429,10 +13596,11 @@ const isKeyboardModality = () => keyboardNavigationUsed;
|
|
|
13429
13596
|
{
|
|
13430
13597
|
// We implement :focus and :focus-visible with enriched semantics:
|
|
13431
13598
|
// an element is considered focused not only when it natively has focus, but also
|
|
13432
|
-
// when a
|
|
13433
|
-
//
|
|
13434
|
-
//
|
|
13435
|
-
// while the input is
|
|
13599
|
+
// when it is a control proxy whose real control has focus (the proxy is what is
|
|
13600
|
+
// seen, the focus is dealt to the real control — see control_proxy.js) or when
|
|
13601
|
+
// a controlling element has focus (e.g. a combobox input with aria-controls
|
|
13602
|
+
// pointing to a listbox — the listbox should appear focused while the input is
|
|
13603
|
+
// focused).
|
|
13436
13604
|
//
|
|
13437
13605
|
// We intentionally reuse the native :focus / :focus-visible names rather than
|
|
13438
13606
|
// introducing new navi-specific pseudo-classes (e.g. :-navi-focus). This is a
|
|
@@ -13524,7 +13692,7 @@ const isKeyboardModality = () => keyboardNavigationUsed;
|
|
|
13524
13692
|
|
|
13525
13693
|
// Returns true when el holds focus indirectly — either because a controlling
|
|
13526
13694
|
// element (aria-controls) has focus, or because el is a proxy whose target
|
|
13527
|
-
// is itself controlled by a focused element.
|
|
13695
|
+
// has focus or is itself controlled by a focused element.
|
|
13528
13696
|
const hasIndirectFocus = (el, { requireFocusVisible = false } = {}) => {
|
|
13529
13697
|
// No ring inheritance without a keyboard: an editable target draws its own
|
|
13530
13698
|
// ring on any focus (see isMatchingFocusVisible), but propagating that ring
|
|
@@ -15063,7 +15231,11 @@ const createOpenController = (
|
|
|
15063
15231
|
const applyChange = () => {
|
|
15064
15232
|
// Recorded with the change itself: what the DOM shows is what a render
|
|
15065
15233
|
// landing between the ask and the picture must draw (see `openedInDom`).
|
|
15066
|
-
|
|
15234
|
+
// An opening records it inside the change, once its content is built
|
|
15235
|
+
// (see open() below).
|
|
15236
|
+
if (!opened) {
|
|
15237
|
+
controller.openedInDom = false;
|
|
15238
|
+
}
|
|
15067
15239
|
change();
|
|
15068
15240
|
};
|
|
15069
15241
|
const { transitionChange } = controller;
|
|
@@ -15343,15 +15515,17 @@ const createOpenController = (
|
|
|
15343
15515
|
controller.mountContent?.();
|
|
15344
15516
|
// Only now — after the content has been built, before openEffect
|
|
15345
15517
|
// shows it. Dialog/Popover recompute aria-expanded and navi-hidden
|
|
15346
|
-
// from
|
|
15518
|
+
// from `openedInDom` on every render, and mountContent above renders
|
|
15347
15519
|
// synchronously: flipping it any earlier commits an already-open DOM
|
|
15348
15520
|
// (aria-expanded "true", navi-hidden gone) before openEffect has run
|
|
15349
15521
|
// a single statement, so the "closed" frame it pins to transition
|
|
15350
15522
|
// from is in fact the open one and the entrance animation has
|
|
15351
|
-
// nothing to play
|
|
15352
|
-
//
|
|
15353
|
-
//
|
|
15354
|
-
// popup_content_mount.js and
|
|
15523
|
+
// nothing to play — on every first opening that builds its content.
|
|
15524
|
+
// It also gives the content it just built the opening it is
|
|
15525
|
+
// documented to observe — mounted while the popup reads as closed,
|
|
15526
|
+
// told it opened right after (see popup_content_mount.js and
|
|
15527
|
+
// use_displayed_layout_effect.js).
|
|
15528
|
+
controller.openedInDom = true;
|
|
15355
15529
|
controller.opened = true;
|
|
15356
15530
|
// Which press it opened during, so the release of that press is not
|
|
15357
15531
|
// read as somebody dismissing it (see openedDuringThisPress).
|
|
@@ -15760,7 +15934,14 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
15760
15934
|
lastRunOpenRef.current = open;
|
|
15761
15935
|
|
|
15762
15936
|
if (isFirstRun) {
|
|
15763
|
-
|
|
15937
|
+
// "interaction" is said even over what the caller holds reading open: a
|
|
15938
|
+
// popup built by the request that opens it — a dialog standing in until
|
|
15939
|
+
// its signal said open, see DialogStandIn in dialog.jsx — mounts BECAUSE
|
|
15940
|
+
// of that opening.
|
|
15941
|
+
const mountOpenReason =
|
|
15942
|
+
open && defaultOpen === "interaction"
|
|
15943
|
+
? "interaction"
|
|
15944
|
+
: open || defaultOpen;
|
|
15764
15945
|
if (mountOpenReason) {
|
|
15765
15946
|
// Whether this popup being open is something that just happened, or
|
|
15766
15947
|
// something that was already true when the page appeared. "interaction"
|
|
@@ -22258,10 +22439,11 @@ const swipeTypeOf = (axis, pulled) => {
|
|
|
22258
22439
|
* insists.
|
|
22259
22440
|
*
|
|
22260
22441
|
* So the interaction stays declared and says "refuse" in place of what it does.
|
|
22261
|
-
* The threshold is the same one — a mouse travelling
|
|
22262
|
-
*
|
|
22263
|
-
*
|
|
22264
|
-
*
|
|
22442
|
+
* The threshold is the same one — a mouse travelling a few pixels, a finger
|
|
22443
|
+
* holding still, or travelling those same few pixels inside a
|
|
22444
|
+
* `data-drag-on-contact` — and at the instant the grab would have been
|
|
22445
|
+
* acquired there is none: nothing translates, no copy is made, no release is
|
|
22446
|
+
* answered. `refuse` is that instant.
|
|
22265
22447
|
*
|
|
22266
22448
|
* It walks no axis, so it takes none: a box that travels and a surface that pans
|
|
22267
22449
|
* both step over an element that is refusing, and a gesture starting on a locked
|
|
@@ -25978,8 +26160,9 @@ const createResourceLifecycleManager = () => {
|
|
|
25978
26160
|
}
|
|
25979
26161
|
|
|
25980
26162
|
// scopedMany auto-dependency: only rerun parent singular GET on child POST,
|
|
25981
|
-
//
|
|
25982
|
-
// inside its response
|
|
26163
|
+
// only the GET of the owner the child was posted to, and only when that
|
|
26164
|
+
// GET previously returned the sub-resource embedded inside its response
|
|
26165
|
+
// (detected via action._resultProperties).
|
|
25983
26166
|
// GET_MANY is excluded — a list of parents is not stale just because one
|
|
25984
26167
|
// child item was added to one of them.
|
|
25985
26168
|
scoped_many_effect: {
|
|
@@ -26000,6 +26183,15 @@ const createResourceLifecycleManager = () => {
|
|
|
26000
26183
|
if (parentResource !== resourceScope) {
|
|
26001
26184
|
continue;
|
|
26002
26185
|
}
|
|
26186
|
+
if (
|
|
26187
|
+
!isOwnerReadBy(
|
|
26188
|
+
triggerResourceScope,
|
|
26189
|
+
triggeringAction,
|
|
26190
|
+
actionCandidate,
|
|
26191
|
+
)
|
|
26192
|
+
) {
|
|
26193
|
+
break scoped_many_effect;
|
|
26194
|
+
}
|
|
26003
26195
|
// Only rerun if the last GET response included the embedded sub-resource.
|
|
26004
26196
|
if (
|
|
26005
26197
|
!getActionResultProperties(actionCandidate)?.has(propertyName)
|
|
@@ -26116,6 +26308,27 @@ const isParamSubset = (parentParams, childParams) => {
|
|
|
26116
26308
|
return true;
|
|
26117
26309
|
};
|
|
26118
26310
|
|
|
26311
|
+
// Whether a GET of the owner read the item a scoped child's action was answered
|
|
26312
|
+
// for. The child's value names its owner first ([ownerKey, …], see scopedMany);
|
|
26313
|
+
// the GET's value is the id of the item it read. The key may be a unique key
|
|
26314
|
+
// rather than that id: both lead to the owner's one store of children. Values
|
|
26315
|
+
// are read off the signals: the child's action is still completing, and its
|
|
26316
|
+
// `value` property is only written once that batch ends.
|
|
26317
|
+
const isOwnerReadBy = (childResource, childAction, ownerGetAction) => {
|
|
26318
|
+
const childValue = childAction.valueSignal.peek();
|
|
26319
|
+
if (!Array.isArray(childValue)) {
|
|
26320
|
+
return false;
|
|
26321
|
+
}
|
|
26322
|
+
const [ownerKey] = childValue;
|
|
26323
|
+
const ownerId = ownerGetAction.valueSignal.peek();
|
|
26324
|
+
const { getChildStore } = childResource;
|
|
26325
|
+
if (!getChildStore) {
|
|
26326
|
+
return ownerKey === ownerId;
|
|
26327
|
+
}
|
|
26328
|
+
const childStore = getChildStore(ownerKey);
|
|
26329
|
+
return childStore !== undefined && childStore === getChildStore(ownerId);
|
|
26330
|
+
};
|
|
26331
|
+
|
|
26119
26332
|
const paramScopeWeakSet = createIterableWeakSet();
|
|
26120
26333
|
let paramScopeIdCounter = 0;
|
|
26121
26334
|
const getParamScope = (params) => {
|
|
@@ -28693,6 +28906,18 @@ const useAsyncData = (promiseOrAction, {
|
|
|
28693
28906
|
|
|
28694
28907
|
// ─── useAction ────────────────────────────────────────────────────────────────
|
|
28695
28908
|
|
|
28909
|
+
// subscribe() calls back at once with the value the signal holds; what these
|
|
28910
|
+
// subscriptions are for is what changes after.
|
|
28911
|
+
const subscribeToChanges = (signal, onChange) => {
|
|
28912
|
+
let initial = true;
|
|
28913
|
+
return signal.subscribe(value => {
|
|
28914
|
+
if (initial) {
|
|
28915
|
+
initial = false;
|
|
28916
|
+
return;
|
|
28917
|
+
}
|
|
28918
|
+
onChange(value);
|
|
28919
|
+
});
|
|
28920
|
+
};
|
|
28696
28921
|
const LoadingContext = createContext(null);
|
|
28697
28922
|
const actionPendingPromiseWeakMap = new WeakMap();
|
|
28698
28923
|
const dismissedActionWeakSet = new WeakSet();
|
|
@@ -28737,6 +28962,15 @@ const useActionAsyncData = (action, {
|
|
|
28737
28962
|
const runningState = action.runningStateSignal.peek();
|
|
28738
28963
|
const [, setTick] = useState(0);
|
|
28739
28964
|
const routePage = useContext(RoutePageContext);
|
|
28965
|
+
// What this render read of the action, for the subscriptions below to tell
|
|
28966
|
+
// a change that came between the render and them from none at all.
|
|
28967
|
+
const readRef = useRef(null);
|
|
28968
|
+
readRef.current = {
|
|
28969
|
+
runningState,
|
|
28970
|
+
data: action.dataSignal.peek(),
|
|
28971
|
+
params: action.paramsSignal.peek(),
|
|
28972
|
+
settling: action.paramsSettlingSignal.peek()
|
|
28973
|
+
};
|
|
28740
28974
|
useEffect(() => {
|
|
28741
28975
|
let stopOwingRender = null;
|
|
28742
28976
|
const rerender = () => {
|
|
@@ -28764,7 +28998,12 @@ const useActionAsyncData = (action, {
|
|
|
28764
28998
|
}
|
|
28765
28999
|
setTick(n => n + 1);
|
|
28766
29000
|
};
|
|
28767
|
-
|
|
29001
|
+
// A run under way is a new try: an error dismissed before it is not the
|
|
29002
|
+
// one it may end on.
|
|
29003
|
+
if (action.runningStateSignal.peek() === RUNNING) {
|
|
29004
|
+
dismissedActionWeakSet.delete(action);
|
|
29005
|
+
}
|
|
29006
|
+
const unsubscribeFromRunningState = subscribeToChanges(action.runningStateSignal, state => {
|
|
28768
29007
|
if (state === RUNNING) {
|
|
28769
29008
|
dismissedActionWeakSet.delete(action);
|
|
28770
29009
|
}
|
|
@@ -28775,38 +29014,24 @@ const useActionAsyncData = (action, {
|
|
|
28775
29014
|
// (a PUT upserting an item that a GET_MANY list already holds) changes the
|
|
28776
29015
|
// data while this action stays COMPLETED. Subscribing here re-renders
|
|
28777
29016
|
// through the same controlled path as the run state, instead of `.value`.
|
|
28778
|
-
|
|
28779
|
-
const unsubscribeFromData = action.dataSignal.subscribe(() => {
|
|
28780
|
-
if (dataNotificationIsInitial) {
|
|
28781
|
-
// subscribe() calls back synchronously with the current value
|
|
28782
|
-
dataNotificationIsInitial = false;
|
|
28783
|
-
return;
|
|
28784
|
-
}
|
|
28785
|
-
rerender();
|
|
28786
|
-
});
|
|
29017
|
+
const unsubscribeFromData = subscribeToChanges(action.dataSignal, rerender);
|
|
28787
29018
|
// The params say WHICH question this is, and this hook reads them: to know
|
|
28788
29019
|
// there is nothing to ask for, and to start the run it owns. A binding
|
|
28789
29020
|
// retargeting from no question to a question — a filter chosen, a first
|
|
28790
29021
|
// character typed — is announced by nothing above: a fresh target is IDLE
|
|
28791
29022
|
// holding no data, the very state the hook already sees.
|
|
28792
|
-
|
|
28793
|
-
const unsubscribeFromParams = action.paramsSignal.subscribe(() => {
|
|
28794
|
-
if (paramsNotificationIsInitial) {
|
|
28795
|
-
paramsNotificationIsInitial = false;
|
|
28796
|
-
return;
|
|
28797
|
-
}
|
|
28798
|
-
rerender();
|
|
28799
|
-
});
|
|
29023
|
+
const unsubscribeFromParams = subscribeToChanges(action.paramsSignal, rerender);
|
|
28800
29024
|
// A debounced binding waits before it retargets, so nothing above changes
|
|
28801
29025
|
// while the delay runs — but what is on screen is already out of date.
|
|
28802
|
-
|
|
28803
|
-
|
|
28804
|
-
|
|
28805
|
-
|
|
28806
|
-
|
|
28807
|
-
|
|
29026
|
+
const unsubscribeFromParamsSettling = subscribeToChanges(action.paramsSettlingSignal, rerender);
|
|
29027
|
+
// Subscribed once the browser has painted, so the action may have moved
|
|
29028
|
+
// since the render: a run that very render started may be over already,
|
|
29029
|
+
// and nothing would say so again. What the render read, against what is
|
|
29030
|
+
// there now — and nothing moved, nothing to draw again.
|
|
29031
|
+
const read = readRef.current;
|
|
29032
|
+
if (action.runningStateSignal.peek() !== read.runningState || action.dataSignal.peek() !== read.data || action.paramsSignal.peek() !== read.params || action.paramsSettlingSignal.peek() !== read.settling) {
|
|
28808
29033
|
rerender();
|
|
28809
|
-
}
|
|
29034
|
+
}
|
|
28810
29035
|
return () => {
|
|
28811
29036
|
unsubscribeFromRunningState();
|
|
28812
29037
|
unsubscribeFromData();
|
|
@@ -34704,11 +34929,13 @@ const bandTheCutMayBeRelaxedTo = (bandBefore, bandAfter, rectAfter) => {
|
|
|
34704
34929
|
* where they are going, at the movement's own pace, each animation at the rate
|
|
34705
34930
|
* that gets it there in that time.
|
|
34706
34931
|
*
|
|
34707
|
-
* Where they visibly stand is computed from the clock THROUGH the easing curve
|
|
34708
|
-
*
|
|
34709
|
-
*
|
|
34710
|
-
*
|
|
34711
|
-
*
|
|
34932
|
+
* Where they visibly stand is computed from the clock THROUGH the easing curve:
|
|
34933
|
+
* a fraction of the way, whatever property carries it — getComputedStyle on
|
|
34934
|
+
* the pseudo-elements gives the animated value of each property, with nothing
|
|
34935
|
+
* saying how far along its keyframes that value is. The rates are handed over
|
|
34936
|
+
* with updatePlaybackRate: in Chromium, the playbackRate setter given a
|
|
34937
|
+
* negative rate puts the pictures at their start at once, and leaves them
|
|
34938
|
+
* there while the clock counts down.
|
|
34712
34939
|
*/
|
|
34713
34940
|
|
|
34714
34941
|
const viewTransitionAnimations = () => {
|
|
@@ -35293,11 +35520,11 @@ const css$12 = /* css */`@property --navi-route-transition-leave {
|
|
|
35293
35520
|
*
|
|
35294
35521
|
* It is a real box, and it must be: what is photographed and clipped IS its
|
|
35295
35522
|
* rectangle. So `display: contents` cannot be used on it — an element with no
|
|
35296
|
-
* box is never captured, the movement plays on nothing
|
|
35297
|
-
* the
|
|
35298
|
-
* so `flex`, `className`, `style` and the rest are there for
|
|
35299
|
-
* application that already has an element holding its pages can mark
|
|
35300
|
-
* with `data-navi-route-transition-area` rather than nesting another.
|
|
35523
|
+
* box is never captured, and the movement then plays on nothing (navi warns
|
|
35524
|
+
* once the pictures are taken). Give it the layout the pages need instead —
|
|
35525
|
+
* it is a `Box`, so `flex`, `className`, `style` and the rest are there for
|
|
35526
|
+
* that. An application that already has an element holding its pages can mark
|
|
35527
|
+
* that one with `data-navi-route-transition-area` rather than nesting another.
|
|
35301
35528
|
*
|
|
35302
35529
|
* @type {import("ignore:preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
35303
35530
|
*/
|
|
@@ -35358,10 +35585,14 @@ const RouteTransitionArea = ({
|
|
|
35358
35585
|
* length of the transition the root carries
|
|
35359
35586
|
* `data-navi-route-transition-type="<type>"` next to
|
|
35360
35587
|
* `data-navi-route-transition="forward"|"back"`, and the application's CSS
|
|
35361
|
-
* defines the movement against the view transition pseudo-elements
|
|
35588
|
+
* defines the movement against the view transition pseudo-elements, for
|
|
35589
|
+
* both groups: `root` when the document travels, `navi-route-transition`
|
|
35590
|
+
* when an area is marked — the root is then unnamed, and a rule written for
|
|
35591
|
+
* `(root)` alone never applies:
|
|
35362
35592
|
*
|
|
35363
35593
|
* :root[data-navi-route-transition-type="spin"][data-navi-route-transition="forward"] {
|
|
35364
|
-
* &::view-transition-new(root)
|
|
35594
|
+
* &::view-transition-new(root),
|
|
35595
|
+
* &::view-transition-new(navi-route-transition) {
|
|
35365
35596
|
* animation-name: my-spin-in;
|
|
35366
35597
|
* }
|
|
35367
35598
|
* }
|
|
@@ -36019,7 +36250,7 @@ const beginTransition = ({
|
|
|
36019
36250
|
// and the movement then plays on the document itself.
|
|
36020
36251
|
const areaElements = document.querySelectorAll(`[${TRANSITION_AREA_ATTRIBUTE}]`);
|
|
36021
36252
|
if (areaElements.length > 1) {
|
|
36022
|
-
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
|
|
36253
|
+
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.`);
|
|
36023
36254
|
}
|
|
36024
36255
|
const areaElement = areaElements.length > 0 ? areaElements[0] : null;
|
|
36025
36256
|
// Everything about to be photographed goes deaf to the pointer; the controls
|
|
@@ -36763,8 +36994,9 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
36763
36994
|
* - how to go to a tab. The default REPLACES the current history entry
|
|
36764
36995
|
* rather than pushing one: a swipe is how one browses a page, not a place one
|
|
36765
36996
|
* aimed at, and three swipes back and forth must not bury the way out of the
|
|
36766
|
-
* page under six entries. A tab pressed
|
|
36767
|
-
*
|
|
36997
|
+
* page under six entries. A tab pressed goes the way its <Link> says —
|
|
36998
|
+
* `<Link replace>` for a row of tabs, so a press and a swipe towards the
|
|
36999
|
+
* same neighbour write the same history.
|
|
36768
37000
|
*
|
|
36769
37001
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`,
|
|
36770
37002
|
* …), which is how one of these is named: a page can hold several — a row of
|
|
@@ -36779,10 +37011,11 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
36779
37011
|
* gesture applies its change on release instead of dragging a picture that does
|
|
36780
37012
|
* not exist.
|
|
36781
37013
|
*
|
|
36782
|
-
* While a travel plays, the rest of the page is taken as a picture
|
|
36783
|
-
*
|
|
36784
|
-
*
|
|
36785
|
-
*
|
|
37014
|
+
* While a travel plays, the rest of the page is NOT taken as a picture: the
|
|
37015
|
+
* root's name is dropped for that time (`view-transition-name: none`), so what
|
|
37016
|
+
* stands around the box — a tab row beside it — stays live and answers a press
|
|
37017
|
+
* mid-travel. The application's own rule for the root applies again the moment
|
|
37018
|
+
* the travel is over.
|
|
36786
37019
|
*/
|
|
36787
37020
|
const RouteTravel = ({
|
|
36788
37021
|
routes: routesProp,
|
|
@@ -37971,9 +38204,9 @@ const writeCover = (edge, element, size) => {
|
|
|
37971
38204
|
|
|
37972
38205
|
/**
|
|
37973
38206
|
* Binds an action to a route: it runs when the route matches, with the params
|
|
37974
|
-
* the effect reads off the address,
|
|
37975
|
-
* Its data, its wait and its failure are read by the
|
|
37976
|
-
* `useAsyncData`.
|
|
38207
|
+
* the effect reads off the address, runs again on a `reload()`, and is aborted
|
|
38208
|
+
* when the route is left. Its data, its wait and its failure are read by the
|
|
38209
|
+
* page through `useAsyncData`.
|
|
37977
38210
|
*
|
|
37978
38211
|
* A page's CODE is declared the same way — the import is one more thing the
|
|
37979
38212
|
* address asks for, started with the data rather than after it:
|
|
@@ -38017,18 +38250,21 @@ const routeAction = (
|
|
|
38017
38250
|
? routes[0].matchingSignal
|
|
38018
38251
|
: anyMatchingRouteSignal(routes);
|
|
38019
38252
|
const readParams = paramsEffect || (() => true);
|
|
38020
|
-
const
|
|
38021
|
-
|
|
38022
|
-
()
|
|
38023
|
-
|
|
38024
|
-
|
|
38025
|
-
|
|
38026
|
-
|
|
38027
|
-
|
|
38028
|
-
|
|
38029
|
-
|
|
38030
|
-
|
|
38031
|
-
|
|
38253
|
+
const readParamsAsked = () => {
|
|
38254
|
+
const matching = routeMatchingSignal.value;
|
|
38255
|
+
const params = readParams();
|
|
38256
|
+
if (!matching) {
|
|
38257
|
+
return null;
|
|
38258
|
+
}
|
|
38259
|
+
return params;
|
|
38260
|
+
};
|
|
38261
|
+
const actionBoundToRoute = actionRunEffect(action, readParamsAsked, options);
|
|
38262
|
+
for (const route of routes) {
|
|
38263
|
+
// Asked of the route's params as they are, like the effect asks them.
|
|
38264
|
+
registerRouteReload(route, () =>
|
|
38265
|
+
untracked(readParamsAsked) ? actionBoundToRoute : null,
|
|
38266
|
+
);
|
|
38267
|
+
}
|
|
38032
38268
|
if (prefetch) {
|
|
38033
38269
|
for (const route of routes) {
|
|
38034
38270
|
if (paramsEffect) {
|
|
@@ -40013,10 +40249,11 @@ const flushSyncRendering = (fn) => {
|
|
|
40013
40249
|
* without the content, and the browser builds it in an idle moment after
|
|
40014
40250
|
* load — so by the time anyone clicks, it is usually already there.
|
|
40015
40251
|
*
|
|
40016
|
-
* "while-opened" is the opposite end: content
|
|
40017
|
-
*
|
|
40018
|
-
*
|
|
40019
|
-
*
|
|
40252
|
+
* "while-opened" is the opposite end: content rebuilt from scratch every
|
|
40253
|
+
* time, a form whose fresh state is its initial state. What kept content
|
|
40254
|
+
* carries into the next opening is an unsent edit — an untouched field
|
|
40255
|
+
* follows a new `defaultValue` on its own (see followDefaultValue in
|
|
40256
|
+
* ui_state_controller.js) — and this is what throws that edit away.
|
|
40020
40257
|
*
|
|
40021
40258
|
* On top of whichever value is picked, intent on the anchor warms the content:
|
|
40022
40259
|
* a pointer entering the popup's anchor, or focus landing in it, builds the
|
|
@@ -40050,9 +40287,11 @@ const flushSyncRendering = (fn) => {
|
|
|
40050
40287
|
* click coming after the release, the browser taking the gesture
|
|
40051
40288
|
* (`pointercancel`), or the next press starting, before any of its own
|
|
40052
40289
|
* handlers run: one press at a time holds a content. A keyboard opening builds
|
|
40053
|
-
* at open time. A popup with an `onOpen` is
|
|
40054
|
-
* before the content is built (see
|
|
40055
|
-
* itself from what it writes must
|
|
40290
|
+
* at open time. A "while-opened" popup with an `onOpen` is not warmed by the
|
|
40291
|
+
* press: that callback runs before the content is built (see
|
|
40292
|
+
* open_controller.js), and a content seeding itself from what it writes must
|
|
40293
|
+
* be built after it. Hover and focus warming, for the other values, builds
|
|
40294
|
+
* ahead of `onOpen` all the same.
|
|
40056
40295
|
*/
|
|
40057
40296
|
|
|
40058
40297
|
|
|
@@ -40384,6 +40623,17 @@ const useDisplayedLayoutEffect = (ref, callback, deps) => {
|
|
|
40384
40623
|
decisionDeferredToRef.current = popupMounting;
|
|
40385
40624
|
return;
|
|
40386
40625
|
}
|
|
40626
|
+
if (
|
|
40627
|
+
(el.tagName === "DIALOG" || el.hasAttribute("popover")) &&
|
|
40628
|
+
!isAncestorOpen(el)
|
|
40629
|
+
) {
|
|
40630
|
+
// Itself a closed <dialog> or [popover]: display:none by the UA's own
|
|
40631
|
+
// rules, which the markup says without asking the layout — asked, every
|
|
40632
|
+
// closed popup mounting would force a style recalculation, and a list
|
|
40633
|
+
// whose rows each hold a few mounts hundreds of them. The observer
|
|
40634
|
+
// below fires as for any element the ancestor hides.
|
|
40635
|
+
return;
|
|
40636
|
+
}
|
|
40387
40637
|
if (!isDisplayedDespiteClosedAncestor(el, ancestor)) {
|
|
40388
40638
|
// Ancestor is closed and took this element off screen with it — skip
|
|
40389
40639
|
// now; the observeAncestorOpenState call below will fire once it
|
|
@@ -50805,12 +51055,13 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
50805
51055
|
* does so by being NAMED, which is all the browser needs: any change played as
|
|
50806
51056
|
* a view transition animates it on the same clock as everything else in that
|
|
50807
51057
|
* transition. Inside a `RouteTravel` that means it follows the pages, and the
|
|
50808
|
-
* thumb dragging them, without either of them being told about the other.
|
|
50809
|
-
*
|
|
50810
|
-
*
|
|
50811
|
-
* arrives with its row.
|
|
50812
|
-
*
|
|
50813
|
-
*
|
|
51058
|
+
* thumb dragging them, without either of them being told about the other.
|
|
51059
|
+
* During a route transition or a route travel it keeps the name only for a
|
|
51060
|
+
* movement between two tabs of the row: when the row is on one side only,
|
|
51061
|
+
* the bar leaves or arrives with its row. Any other view transition finds it
|
|
51062
|
+
* named, a picture of its own. For a nav made of slides (`slideContainer`)
|
|
51063
|
+
* the bar is one element for the whole row, and it reads the travel the
|
|
51064
|
+
* container publishes.
|
|
50814
51065
|
* @param {string} [props.slideContainer] - the id of a `<SlideContainer>` these
|
|
50815
51066
|
* tabs are about: each one says which slide it is (`<Link slide="…">`), the
|
|
50816
51067
|
* container says which one is on screen, and pressing a tab travels there.
|
|
@@ -52642,9 +52893,9 @@ const FixedBar = ({
|
|
|
52642
52893
|
* A page that overflows horizontally is a bug wherever it happens, and on
|
|
52643
52894
|
* Chrome Android it is a catastrophic one: the layout viewport inflates to the
|
|
52644
52895
|
* content and `position: fixed` centering goes with it (see
|
|
52645
|
-
* docs/
|
|
52646
|
-
* `overflow-x: clip`, and it works — at the price of making the
|
|
52647
|
-
* invisible: nothing sticks out anymore, so nothing says a fixed width, a
|
|
52896
|
+
* docs/safe_area.md#a-document-wider-than-the-screen). The remedy there is a
|
|
52897
|
+
* wrapper in `overflow-x: clip`, and it works — at the price of making the
|
|
52898
|
+
* cause invisible: nothing sticks out anymore, so nothing says a fixed width, a
|
|
52648
52899
|
* `min-width` or an unbreakable string is still oversized. This puts the
|
|
52649
52900
|
* signal back, without giving up the net.
|
|
52650
52901
|
*
|
|
@@ -55322,7 +55573,8 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
55322
55573
|
* `command` runs at once — the popup it sits in closes over the run, which
|
|
55323
55574
|
* goes on behind. A failure then has no popup to be read in: `resetOnError`
|
|
55324
55575
|
* puts the button back and the error callout is drawn on what surrounds the
|
|
55325
|
-
* closed popup (see
|
|
55576
|
+
* closed popup (see
|
|
55577
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
55326
55578
|
* @param {boolean} [actionAbortable] The person waiting may give up on this
|
|
55327
55579
|
* button's action: closing the popup the run holds calls it off and goes
|
|
55328
55580
|
* through, instead of being refused. For a run whose answer may never come —
|
|
@@ -65250,7 +65502,7 @@ const css$E = /* css */`
|
|
|
65250
65502
|
the length of the movement, and the picture of the lifted node comes out
|
|
65251
65503
|
empty (Chrome 153, reproduced in a bare page). The price is a page frozen
|
|
65252
65504
|
and unpressable while the movement plays: under a modal wall at the
|
|
65253
|
-
opening, and for
|
|
65505
|
+
opening, and for the popup's --popup-animation-duration at the closing. */
|
|
65254
65506
|
:root[data-navi-popup-lift] {
|
|
65255
65507
|
/* The popup's own pace, published on the root by popup_lift.js because the
|
|
65256
65508
|
::view-transition tree hangs off it and inherits from nowhere else. */
|
|
@@ -65594,16 +65846,21 @@ const css$E = /* css */`
|
|
|
65594
65846
|
* @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
|
|
65595
65847
|
* Where the keyboard goes when this dialog opens — one rung of the ladder in
|
|
65596
65848
|
* `docs/autofocus.md`, which is what to read for the whole of it.
|
|
65597
|
-
* - `true` — the dialog element itself takes the keyboard
|
|
65598
|
-
*
|
|
65599
|
-
*
|
|
65849
|
+
* - `true` — the dialog element itself takes the keyboard rather than the
|
|
65850
|
+
* first focusable it holds; something inside that asks by name
|
|
65851
|
+
* (`autoFocus`) still comes first. For a dialog whose content is READ
|
|
65852
|
+
* before it is filled: the focus starts at the top of the reading
|
|
65853
|
+
* order and no virtual keyboard rises over it.
|
|
65600
65854
|
* - `"last-resort"` — the dialog takes the keyboard only if it holds nothing
|
|
65601
65855
|
* focusable of its own.
|
|
65602
65856
|
* - `"restore"` — the dialog stays out of the opening focus chain unless it
|
|
65603
65857
|
* held focus when it closed.
|
|
65604
|
-
* - `false` — the dialog
|
|
65605
|
-
*
|
|
65606
|
-
*
|
|
65858
|
+
* - `false` — the dialog says nothing, and the opening still hands out the
|
|
65859
|
+
* focus: a `<dialog>` is focusable and the first focusable of its own
|
|
65860
|
+
* subtree, so it takes the keyboard as with `true`. Where the first
|
|
65861
|
+
* focusable is skipped (a touch device, below), a last resort inside is
|
|
65862
|
+
* tried instead, and nothing there leaves the keyboard where it was.
|
|
65863
|
+
* Unlike `Popover`'s `false`, the transfer runs.
|
|
65607
65864
|
* Wherever the keyboard is a virtual one (a touch device), the surface is
|
|
65608
65865
|
* already what one arrives on: a popup is read before it is reached there, so
|
|
65609
65866
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
@@ -65637,9 +65894,9 @@ const css$E = /* css */`
|
|
|
65637
65894
|
* is "new" or "edit X" depending on the press must know which one it is
|
|
65638
65895
|
* before what it holds is rendered.
|
|
65639
65896
|
* @param {(event: Event) => void} [props.onClose] - Called when the dialog
|
|
65640
|
-
* actually closes — not preventable
|
|
65641
|
-
*
|
|
65642
|
-
*
|
|
65897
|
+
* actually closes — not preventable: it is the close happening, not a
|
|
65898
|
+
* request. What refuses a close is navi's: a control inside still
|
|
65899
|
+
* running its action (see popup_busy.js for the runs that let it through).
|
|
65643
65900
|
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
65644
65901
|
* Keeps the open state in the history entry, so a screen left and come back
|
|
65645
65902
|
* to finds this popup as it was — open, and without an entrance playing: it
|
|
@@ -65657,14 +65914,17 @@ const css$E = /* css */`
|
|
|
65657
65914
|
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
|
|
65658
65915
|
* `children` are built and thrown away (see popup_content_mount.js).
|
|
65659
65916
|
* `"from-first-open"` (the default) builds them on the first open and keeps
|
|
65660
|
-
* them afterwards
|
|
65917
|
+
* them afterwards — and the dialog itself is built then too: until its first
|
|
65918
|
+
* request (an open, a press on what opens it, its signal saying open) it is
|
|
65919
|
+
* a bare `<dialog>` holding its id (see docs/popup_open.md). `"always"` builds them right away, for content something
|
|
65661
65920
|
* depends on while the popup is still closed: a value read off it, fields a
|
|
65662
65921
|
* surrounding form collects on submit, a size measured from outside.
|
|
65663
65922
|
* `"idle"` builds them in a browser idle moment after load — "always" minus
|
|
65664
65923
|
* the cost on the critical render. `"while-opened"` throws them away once
|
|
65665
65924
|
* the popup has finished closing, for content whose fresh state is its
|
|
65666
|
-
* initial state: an
|
|
65667
|
-
*
|
|
65925
|
+
* initial state: an edit left unsent at the close does not come back at
|
|
65926
|
+
* the next opening (an untouched field needs none of this to show a new
|
|
65927
|
+
* `defaultValue` — it follows one). Intent on the anchor (pointer
|
|
65668
65928
|
* entering it, focus landing in it) builds the content ahead of the click;
|
|
65669
65929
|
* under `"while-opened"`, only a press on what opens the popup does (a
|
|
65670
65930
|
* `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
|
|
@@ -65678,11 +65938,122 @@ const Dialog = props => {
|
|
|
65678
65938
|
...props
|
|
65679
65939
|
});
|
|
65680
65940
|
}
|
|
65681
|
-
return jsx(
|
|
65941
|
+
return jsx(DialogStandIn, {
|
|
65682
65942
|
...props
|
|
65683
65943
|
});
|
|
65684
65944
|
};
|
|
65685
65945
|
|
|
65946
|
+
// A dialog nobody has asked for yet stands in as its bare <dialog>. That
|
|
65947
|
+
// element is all that reaches a closed dialog before its first opening: a
|
|
65948
|
+
// command finds it by id (`commandfor`) and asks it to open, a press on what
|
|
65949
|
+
// opens it announces itself on it (navi_open_press), --navi-toggle reads its
|
|
65950
|
+
// aria-expanded. The dialog proper — its open controller, its box, its focus
|
|
65951
|
+
// and pseudo-class wiring — is built at the first request: an open, a press
|
|
65952
|
+
// about to open it, its signal or its `open` saying open. A list whose rows
|
|
65953
|
+
// each hold a few dialogs mounts hundreds of them, and would pay for every one
|
|
65954
|
+
// at every mount, for openings that mostly never come. Built once, the dialog
|
|
65955
|
+
// stays: the next opening finds it there.
|
|
65956
|
+
const DialogStandIn = props => {
|
|
65957
|
+
const {
|
|
65958
|
+
ref,
|
|
65959
|
+
id,
|
|
65960
|
+
signal,
|
|
65961
|
+
value,
|
|
65962
|
+
open
|
|
65963
|
+
} = props;
|
|
65964
|
+
const ownRef = useRef();
|
|
65965
|
+
const dialogRef = ref || ownRef;
|
|
65966
|
+
// Why the dialog proper exists, once it does; never undone.
|
|
65967
|
+
const builtRef = useRef(null);
|
|
65968
|
+
const mountedRef = useRef(false);
|
|
65969
|
+
const isMount = !mountedRef.current;
|
|
65970
|
+
mountedRef.current = true;
|
|
65971
|
+
// Its own reading of the signal: the dialog of one card among many, told
|
|
65972
|
+
// apart by `value`, follows whether IT is the one open rather than every
|
|
65973
|
+
// write the others cause.
|
|
65974
|
+
const openedSignal = useMemo(() => signal ? computed(() => readOpened(signal.value, value)) : null, [signal, value]);
|
|
65975
|
+
const openedBySignal = openedSignal ? openedSignal.value : false;
|
|
65976
|
+
if (builtRef.current === null) {
|
|
65977
|
+
if (isMount && (needsDialogFromMount(props) || open || openedBySignal)) {
|
|
65978
|
+
builtRef.current = "mount";
|
|
65979
|
+
} else if (open || openedBySignal) {
|
|
65980
|
+
// Asked open while this stood in: the opening is happening now, and it
|
|
65981
|
+
// plays like any other (see defaultOpen "interaction").
|
|
65982
|
+
builtRef.current = "interaction";
|
|
65983
|
+
}
|
|
65984
|
+
}
|
|
65985
|
+
const [, setBuildCount] = useState(0);
|
|
65986
|
+
const warmRef = useRef(null);
|
|
65987
|
+
const build = reason => {
|
|
65988
|
+
if (builtRef.current !== null) {
|
|
65989
|
+
return;
|
|
65990
|
+
}
|
|
65991
|
+
builtRef.current = reason;
|
|
65992
|
+
flushSyncRendering(() => {
|
|
65993
|
+
setBuildCount(count => count + 1);
|
|
65994
|
+
});
|
|
65995
|
+
};
|
|
65996
|
+
const builtReason = builtRef.current;
|
|
65997
|
+
if (builtReason !== null) {
|
|
65998
|
+
return jsx(UncontrolledDialog, {
|
|
65999
|
+
...props,
|
|
66000
|
+
ref: dialogRef,
|
|
66001
|
+
defaultOpen: builtReason === "interaction" ? "interaction" : props.defaultOpen
|
|
66002
|
+
});
|
|
66003
|
+
}
|
|
66004
|
+
return jsx("dialog", {
|
|
66005
|
+
ref: dialogRef,
|
|
66006
|
+
id: id,
|
|
66007
|
+
className: "navi_dialog"
|
|
66008
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
66009
|
+
,
|
|
66010
|
+
"navi-hidden": ""
|
|
66011
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
66012
|
+
,
|
|
66013
|
+
"navi-out-of-flow": "",
|
|
66014
|
+
"aria-expanded": "false",
|
|
66015
|
+
"data-testid": props["data-testid"]
|
|
66016
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
66017
|
+
,
|
|
66018
|
+
onnavi_request_open: e => {
|
|
66019
|
+
// Built in this same event, and asked again: the opening is the one
|
|
66020
|
+
// the dialog proper would have had — its source, its anchor, its value.
|
|
66021
|
+
build("request");
|
|
66022
|
+
const opened = dispatchCustomEvent(dialogRef.current, "navi_request_open", e.detail);
|
|
66023
|
+
if (!opened) {
|
|
66024
|
+
e.preventDefault();
|
|
66025
|
+
}
|
|
66026
|
+
}
|
|
66027
|
+
// eslint-disable-next-line react/no-unknown-property
|
|
66028
|
+
,
|
|
66029
|
+
onnavi_open_press: () => {
|
|
66030
|
+
// What the opening will read is asked for at once, as the dialog
|
|
66031
|
+
// proper does (see open_controller.js); the dialog itself two frames
|
|
66032
|
+
// later, so the pressed trigger is painted first — the click that
|
|
66033
|
+
// follows opens it built, or builds it itself when it comes sooner.
|
|
66034
|
+
if (signal) {
|
|
66035
|
+
preloadState(signal, readOpenValue(value));
|
|
66036
|
+
}
|
|
66037
|
+
if (warmRef.current) {
|
|
66038
|
+
return;
|
|
66039
|
+
}
|
|
66040
|
+
warmRef.current = requestFrameAfterNext(() => {
|
|
66041
|
+
warmRef.current = null;
|
|
66042
|
+
build("press");
|
|
66043
|
+
});
|
|
66044
|
+
}
|
|
66045
|
+
});
|
|
66046
|
+
};
|
|
66047
|
+
// What needs the dialog proper from the start: an opening at mount, content
|
|
66048
|
+
// built ahead of any opening, a history entry to read, an anchor whose hover
|
|
66049
|
+
// or focus builds the content ahead of the click.
|
|
66050
|
+
const needsDialogFromMount = props => {
|
|
66051
|
+
if (props.defaultOpen || props.navState || props.anchor !== undefined) {
|
|
66052
|
+
return true;
|
|
66053
|
+
}
|
|
66054
|
+
return props.mount !== undefined && props.mount !== "from-first-open";
|
|
66055
|
+
};
|
|
66056
|
+
|
|
65686
66057
|
// No openController passed: this Dialog is used declaratively (e.g. driven
|
|
65687
66058
|
// by --navi-toggle/--navi-open/--navi-close commands, the `open` prop, or
|
|
65688
66059
|
// `defaultOpen`) rather than owned by a parent component.
|
|
@@ -66242,10 +66613,10 @@ const useDialogProps = props => {
|
|
|
66242
66613
|
}
|
|
66243
66614
|
|
|
66244
66615
|
// Suppressed until committed below — same @starting-style-avoidance
|
|
66245
|
-
// reasoning as popover.jsx's own openEffect (see
|
|
66246
|
-
// though Dialog never needs to measure/flip anything: it
|
|
66247
|
-
// genuinely rendered "closed" frame to transition from, not a
|
|
66248
|
-
// straight from not-shown to aria-expanded="true".
|
|
66616
|
+
// reasoning as popover.jsx's own openEffect (see popup_css.js's top
|
|
66617
|
+
// comment), even though Dialog never needs to measure/flip anything: it
|
|
66618
|
+
// still needs a genuinely rendered "closed" frame to transition from, not a
|
|
66619
|
+
// jump straight from not-shown to aria-expanded="true".
|
|
66249
66620
|
dialogEl.style.transitionProperty = "none";
|
|
66250
66621
|
if (backdropEl) {
|
|
66251
66622
|
disarmBackdropHideRef.current?.();
|
|
@@ -67384,10 +67755,11 @@ const css$D = /* css */`
|
|
|
67384
67755
|
* @param {boolean|"last-resort"|"restore"} [props.autoFocus="last-resort"] -
|
|
67385
67756
|
* Where the keyboard goes when this popover opens — one rung of the ladder in
|
|
67386
67757
|
* `docs/autofocus.md`, which is what to read for the whole of it.
|
|
67387
|
-
* - `true` — the popover element itself takes the keyboard
|
|
67388
|
-
*
|
|
67389
|
-
*
|
|
67390
|
-
* it
|
|
67758
|
+
* - `true` — the popover element itself takes the keyboard rather than the
|
|
67759
|
+
* first focusable it holds; something inside that asks by name
|
|
67760
|
+
* (`autoFocus`) still comes first. For a popover whose content is READ
|
|
67761
|
+
* before it is filled: the focus starts at the top of the reading
|
|
67762
|
+
* order and no virtual keyboard rises over it.
|
|
67391
67763
|
* - `"last-resort"` — the popover takes the keyboard only if it holds nothing
|
|
67392
67764
|
* focusable of its own.
|
|
67393
67765
|
* - `"restore"` — the popover stays out of the opening focus chain unless it
|
|
@@ -67428,9 +67800,9 @@ const css$D = /* css */`
|
|
|
67428
67800
|
* is "new" or "edit X" depending on the press must know which one it is
|
|
67429
67801
|
* before what it holds is rendered.
|
|
67430
67802
|
* @param {(event: Event) => void} [props.onClose] - Called when the popover
|
|
67431
|
-
* actually closes — not preventable
|
|
67432
|
-
*
|
|
67433
|
-
*
|
|
67803
|
+
* actually closes — not preventable: it is the close happening, not a
|
|
67804
|
+
* request. What refuses a close is navi's: a control inside still
|
|
67805
|
+
* running its action (see popup_busy.js for the runs that let it through).
|
|
67434
67806
|
* @param {boolean|string|{id?: string, type?: "push"|"replace"}} [props.navState] -
|
|
67435
67807
|
* Keeps the open state in the history entry, so a screen left and come back
|
|
67436
67808
|
* to finds this popup as it was — open, and without an entrance playing: it
|
|
@@ -67454,8 +67826,9 @@ const css$D = /* css */`
|
|
|
67454
67826
|
* `"idle"` builds them in a browser idle moment after load — "always" minus
|
|
67455
67827
|
* the cost on the critical render. `"while-opened"` throws them away once
|
|
67456
67828
|
* the popup has finished closing, for content whose fresh state is its
|
|
67457
|
-
* initial state: an
|
|
67458
|
-
*
|
|
67829
|
+
* initial state: an edit left unsent at the close does not come back at
|
|
67830
|
+
* the next opening (an untouched field needs none of this to show a new
|
|
67831
|
+
* `defaultValue` — it follows one). Intent on the anchor (pointer
|
|
67459
67832
|
* entering it, focus landing in it) builds the content ahead of the click;
|
|
67460
67833
|
* under `"while-opened"`, only a press on what opens the popup does (a
|
|
67461
67834
|
* `--navi-open` button, a picker's trigger), never with an `onOpen`, and a
|
|
@@ -67888,7 +68261,7 @@ const usePopoverProps = props => {
|
|
|
67888
68261
|
});
|
|
67889
68262
|
|
|
67890
68263
|
// Suppressed until the popover is actually measured/positioned below —
|
|
67891
|
-
// see
|
|
68264
|
+
// see popup_css.js's top comment for why @starting-style can't drive the
|
|
67892
68265
|
// opening transition (it needs the popover's actual resting position
|
|
67893
68266
|
// already in place, which requires a layout box that only exists once
|
|
67894
68267
|
// shown).
|
|
@@ -68236,7 +68609,7 @@ const usePopoverProps = props => {
|
|
|
68236
68609
|
// frame set up above as a real rendered state (the reflow), re-enables
|
|
68237
68610
|
// transitions, flips aria-expanded="true" (only then does the CSS
|
|
68238
68611
|
// transition play, from that just-committed frame to the open one,
|
|
68239
|
-
// with no @starting-style involved at all — see
|
|
68612
|
+
// with no @starting-style involved at all — see popup_css.js's top
|
|
68240
68613
|
// comment), suppresses pointer events until it settles, and transfers
|
|
68241
68614
|
// focus in. Inlined rather than a standalone function since it only
|
|
68242
68615
|
// has this one call site.
|
|
@@ -68724,8 +69097,9 @@ const css$C = /* css */`@layer navi {
|
|
|
68724
69097
|
* `"idle"` builds them in a browser idle moment after load — `"always"`
|
|
68725
69098
|
* minus the cost on the critical render.
|
|
68726
69099
|
* `"while-opened"` throws them away once the popup has finished closing, for
|
|
68727
|
-
* content whose fresh state is its initial state: an
|
|
68728
|
-
*
|
|
69100
|
+
* content whose fresh state is its initial state: an edit left unsent at
|
|
69101
|
+
* the close does not come back at the next opening (an untouched field
|
|
69102
|
+
* needs none of this to show a new `defaultValue` — it follows one).
|
|
68729
69103
|
* @param {import("ignore:preact").ComponentChildren} props.children
|
|
68730
69104
|
*/
|
|
68731
69105
|
const Popup = props => {
|
|
@@ -69024,8 +69398,9 @@ const PickerCustom = props => {
|
|
|
69024
69398
|
open,
|
|
69025
69399
|
defaultOpen,
|
|
69026
69400
|
// What Escape means for this picker. "cancel" (the default) puts back the
|
|
69027
|
-
// value the picker had at open and, for a dialog
|
|
69028
|
-
// so everything written to the url while it was open
|
|
69401
|
+
// value the picker had at open and, for a dialog with an explicit id, goes
|
|
69402
|
+
// back in history — so everything written to the url while it was open
|
|
69403
|
+
// goes back too (see pickerNavType below).
|
|
69029
69404
|
// "close" makes Escape say the same thing as clicking outside: keep what
|
|
69030
69405
|
// was chosen, close the popup.
|
|
69031
69406
|
escapeEffect = "cancel",
|
|
@@ -69693,9 +70068,10 @@ const PickerContentInsidePopup = props => {
|
|
|
69693
70068
|
// itself, and this one does not. Popover ignores it, same as Dialog ignores
|
|
69694
70069
|
// marginWithAnchor.
|
|
69695
70070
|
dockedOnSmallTouchScreen,
|
|
69696
|
-
// Same again: the dialog takes the trigger's box as a floor (
|
|
69697
|
-
//
|
|
69698
|
-
//
|
|
70071
|
+
// Same again: the dialog takes the trigger's box as a floor (Dialog's own
|
|
70072
|
+
// `sizeFromAnchor`) and, unless a dialogMaxWidth says otherwise, as a
|
|
70073
|
+
// ceiling (see maxWidth below) — what keeps a card its own width once
|
|
70074
|
+
// lifted.
|
|
69699
70075
|
dialogSizeFromAnchor,
|
|
69700
70076
|
// The caller's word on the popup's own box. Written on the popup element,
|
|
69701
70077
|
// where Popover/Dialog map them to their own vars, rather than as vars on
|
|
@@ -77527,10 +77903,10 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
77527
77903
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
77528
77904
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
77529
77905
|
* placeholder text: `fallback="Select skills…"`. Plain text is the right
|
|
77530
|
-
* choice there: it reads at the picker's size, in the
|
|
77531
|
-
*
|
|
77532
|
-
* transparent <Badge> matches the slot to the pixel instead, at the
|
|
77533
|
-
* badge-sized text. See docs/
|
|
77906
|
+
* choice there: it reads at the picker's size, in the normal text color (a
|
|
77907
|
+
* caller's `ui` is never greyed as a placeholder), and the box stays the same
|
|
77908
|
+
* height. A transparent <Badge> matches the slot to the pixel instead, at the
|
|
77909
|
+
* price of badge-sized text. See docs/typography.md#badgelist.
|
|
77534
77910
|
* @param {boolean} [shrinkWrap]
|
|
77535
77911
|
* Narrows the list down to its widest row so the last row isn't ragged.
|
|
77536
77912
|
* Defaults to true inside a <Picker> — the trigger draws a border around the
|
|
@@ -79675,6 +80051,7 @@ const PickerFirstResolver = props => {
|
|
|
79675
80051
|
* dialogExpandX?: boolean,
|
|
79676
80052
|
* dialogExpandY?: boolean,
|
|
79677
80053
|
* dialogSizeFromAnchor?: boolean,
|
|
80054
|
+
* dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
|
|
79678
80055
|
* marginWithContainer?: number | string,
|
|
79679
80056
|
* anchor?: import("ignore:preact").RefObject<HTMLElement> | HTMLElement,
|
|
79680
80057
|
* escapeEffect?: "cancel" | "close",
|
|
@@ -79731,8 +80108,8 @@ const PickerFirstResolver = props => {
|
|
|
79731
80108
|
* filter", "anywhere"), so its empty look is the caller's. A `ui` must
|
|
79732
80109
|
* therefore say on its own that the picker is empty, and take the same room
|
|
79733
80110
|
* empty as it does filled. A <BadgeList> gets both from its `fallback` — the
|
|
79734
|
-
* placeholder text, as plain text, see docs/
|
|
79735
|
-
* nothing in the slot at all.
|
|
80111
|
+
* placeholder text, as plain text, see docs/typography.md#badgelist.
|
|
80112
|
+
* `"default"` draws nothing in the slot at all.
|
|
79736
80113
|
*
|
|
79737
80114
|
* Under `variant="icon"` there is no value to draw, so this IS the trigger:
|
|
79738
80115
|
* left out it is the icon the right slot would have shown (`rightSlotIcon`,
|
|
@@ -80053,14 +80430,23 @@ const PickerFirstResolver = props => {
|
|
|
80053
80430
|
* about it changes. `dialogMaxWidth="var(--anchor-width)"` says the same
|
|
80054
80431
|
* ceiling by hand: the popup props are written on the dialog element, where
|
|
80055
80432
|
* that variable lives.
|
|
80433
|
+
* @param {boolean|"top"|"bottom"} [dockedOnSmallTouchScreen] Dialog mode,
|
|
80434
|
+
* Dialog's own: on a small touch screen the dialog becomes a sheet docked
|
|
80435
|
+
* full width to one edge — `true` or `"top"` the top edge, out of the
|
|
80436
|
+
* virtual keyboard's way; `"bottom"` the bottom one, for a sheet that is
|
|
80437
|
+
* read and tapped. Anywhere else it stays the centered box. Ignored in
|
|
80438
|
+
* popover mode.
|
|
80056
80439
|
* @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
|
|
80057
80440
|
* picker. "cancel" puts back the value the picker had at open, and a dialog
|
|
80058
|
-
* picker
|
|
80059
|
-
*
|
|
80060
|
-
*
|
|
80061
|
-
*
|
|
80062
|
-
*
|
|
80063
|
-
*
|
|
80441
|
+
* picker with an explicit `id` (its opening being an entry of its own)
|
|
80442
|
+
* also goes back in history — so anything written to the url while it was
|
|
80443
|
+
* open (a route `stateSignal`, a search param) goes back with it. "close"
|
|
80444
|
+
* makes Escape say what clicking outside says: keep what was chosen,
|
|
80445
|
+
* close — a last resort, see
|
|
80446
|
+
* docs/popup_open.md#escape-cancels-the-other-gestures-keep for why Escape
|
|
80447
|
+
* should go on meaning cancel, and
|
|
80448
|
+
* docs/popup_open.md#what-cancel-actually-undoes for what a cancel puts
|
|
80449
|
+
* back.
|
|
80064
80450
|
* @param {import("ignore:preact").RefObject<HTMLElement>|HTMLElement} [anchor] What
|
|
80065
80451
|
* the popup hangs off, when that is not the picker itself: a picker whose
|
|
80066
80452
|
* trigger is one piece of a bigger control (the chevron half of a split
|
|
@@ -80115,7 +80501,9 @@ const PickerFirstResolver = props => {
|
|
|
80115
80501
|
* positioned ancestor for `popupLayer="local"`). Caps the popup's size as
|
|
80116
80502
|
* well as its placement, so what an expanded dialog leaves visible around
|
|
80117
80503
|
* itself is set here. Defaults to `popoverSpacing` in popover mode, and to
|
|
80118
|
-
* Dialog's own
|
|
80504
|
+
* Dialog's own in dialog mode: `3appw` in the top layer, `3cqw` under
|
|
80505
|
+
* `popupLayer="local"`, 0 for a dialog docked by
|
|
80506
|
+
* `dockedOnSmallTouchScreen`.
|
|
80119
80507
|
* @param {string} [popupTestId] The `data-testid` of the popup element — the
|
|
80120
80508
|
* popover or dialog the children open in. A `data-testid` on the picker
|
|
80121
80509
|
* itself names the trigger (see docs/testid.md); this one is for a test that
|