@jsenv/navi 0.29.388 → 0.29.390
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/jsenv_navi.js +415 -181
- package/dist/dev/jsenv_navi.js.map +20 -20
- package/dist/dev/jsenv_navi_side_effects.js +87 -78
- package/dist/dev/jsenv_navi_side_effects.js.map +4 -4
- package/dist/jsenv_navi.js +414 -180
- package/dist/jsenv_navi.js.map +20 -20
- package/dist/jsenv_navi_side_effects.js +87 -78
- package/dist/jsenv_navi_side_effects.js.map +4 -4
- package/docs/dialog_shape.md +3 -2
- package/docs/drag_interactions.md +13 -5
- package/docs/drag_to_travel.md +3 -1
- package/docs/error_handling.md +5 -3
- package/docs/i18n.md +16 -7
- package/docs/list_refresh.md +5 -4
- package/docs/network_policy.md +12 -4
- package/docs/pan_zoom.md +24 -10
- package/docs/resource.md +53 -5
- package/docs/safe_area.md +28 -24
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -527,7 +527,10 @@ effect(() => {
|
|
|
527
527
|
* The active language is read from `languagesSignal` (see lang_signal.js —
|
|
528
528
|
* combines the browser's own `navigator.languages`, an optional
|
|
529
529
|
* `setPreferredLanguage()` user override, and an optional
|
|
530
|
-
* `setSupportedLanguages()` app-wide allow-list), live on every lookup.
|
|
530
|
+
* `setSupportedLanguages()` app-wide allow-list), live on every lookup. Each
|
|
531
|
+
* key is looked up in those languages in order, then in English, which
|
|
532
|
+
* translates every key: a language shipped for part of the keys only (German
|
|
533
|
+
* has the `time.*` words) never shows a raw key.
|
|
531
534
|
*
|
|
532
535
|
* Built-in key namespaces, all overridable — the registrations below are the
|
|
533
536
|
* exhaustive list, read them to find the exact key to override:
|
|
@@ -1447,19 +1450,25 @@ const isNetworkPolicyError = (error) => {
|
|
|
1447
1450
|
* is already saying it in words the person understands.
|
|
1448
1451
|
*
|
|
1449
1452
|
* navi's own report leaves it alone (action_error_report.js). What is left is
|
|
1450
|
-
* the
|
|
1451
|
-
*
|
|
1452
|
-
*
|
|
1453
|
-
*
|
|
1454
|
-
* asked. Cancelling the event is what says it is handled: the browser drops
|
|
1453
|
+
* the error reaching `window` on its own — a run called by hand whose failure
|
|
1454
|
+
* nobody catches, a render throw no boundary took — where it lands as an
|
|
1455
|
+
* uncaught error in the console, about a request the app itself declared would
|
|
1456
|
+
* not leave. Cancelling the event is what says it is handled: the browser drops
|
|
1455
1457
|
* the console line, and the jsenv supervisor skips prevented events too.
|
|
1456
1458
|
*
|
|
1457
1459
|
* Both shapes a failure travels in are covered, since which one it is depends
|
|
1458
1460
|
* on whether the resource callback happened to be async — the throw reaching
|
|
1459
1461
|
* `window`, and the rejection nobody caught.
|
|
1460
1462
|
*
|
|
1461
|
-
*
|
|
1462
|
-
*
|
|
1463
|
+
* A boundary displaying it raises no event at all. In dev, `preact/debug` hands
|
|
1464
|
+
* every error a boundary caught to `console.error`, after the boundary took it
|
|
1465
|
+
* and before it rendered anything; it reads no option and no flag, so that line
|
|
1466
|
+
* stays. Muting it from here is not done: wrapping `console.error` rewrites a
|
|
1467
|
+
* global, and wrapping `options._catchError` reaches into preact's private
|
|
1468
|
+
* hooks, both to hide one class of error.
|
|
1469
|
+
*
|
|
1470
|
+
* Scoped to the policy's own error and to nothing else — every other error
|
|
1471
|
+
* thrown at window, and every other rejection let go, stays exactly as loud as
|
|
1463
1472
|
* it is.
|
|
1464
1473
|
*/
|
|
1465
1474
|
let networkPolicyErrorSilenced = false;
|
|
@@ -1620,8 +1629,8 @@ const markErrorAsStoppingRender = (error) => {
|
|
|
1620
1629
|
* An error the network policy produced is never reported: the app declared the
|
|
1621
1630
|
* state that produced it, the request never left, and there is nothing for a
|
|
1622
1631
|
* developer to fix. It is data a screen shows, and it stays data whether or not
|
|
1623
|
-
* one does (see network_policy.js, which also
|
|
1624
|
-
*
|
|
1632
|
+
* one does (see network_policy.js, which also cancels it when it reaches
|
|
1633
|
+
* window).
|
|
1625
1634
|
*/
|
|
1626
1635
|
const errorIsAccountedFor = (error) => {
|
|
1627
1636
|
return (
|
|
@@ -4785,14 +4794,11 @@ const CONTROL_TYPE_BY_SIGNAL_TYPE = {
|
|
|
4785
4794
|
// become something plainer — it is what says the value is a number (see
|
|
4786
4795
|
// isNumberInput), and what lets constraint messages use domain-specific
|
|
4787
4796
|
// wording instead of the generic "Ce nombre doit être...".
|
|
4797
|
+
// A type a picker preset draws (navi_time, navi_minute: see picker_preset.jsx)
|
|
4798
|
+
// has no row: resolveInputProps runs before the preset resolver, and a row
|
|
4799
|
+
// would turn the type into its host before the preset resolver reads it. The
|
|
4800
|
+
// preset hands the host type on itself.
|
|
4788
4801
|
const NAVI_TYPE_DEFAULTS = {
|
|
4789
|
-
navi_time: {
|
|
4790
|
-
"type": "time",
|
|
4791
|
-
"navi-input-type": "time",
|
|
4792
|
-
"min": 0,
|
|
4793
|
-
"max": 24 * 3600 - 1,
|
|
4794
|
-
"step": 1,
|
|
4795
|
-
},
|
|
4796
4802
|
navi_percentage: {
|
|
4797
4803
|
"type": "navi_number",
|
|
4798
4804
|
"navi-input-type": "percentage",
|
|
@@ -4854,7 +4860,6 @@ const isNumberInput = (type, naviInputType) => {
|
|
|
4854
4860
|
* Supported navi types and their targets:
|
|
4855
4861
|
* - `navi_percentage` → `navi_number` (0–100, step 1)
|
|
4856
4862
|
* - `navi_number` → `text` (inputMode="numeric", no spin buttons implied)
|
|
4857
|
-
* - `navi_time` → `time` (step in seconds)
|
|
4858
4863
|
*
|
|
4859
4864
|
* Standard HTML input types with formatters:
|
|
4860
4865
|
* - `date`, `month`, `week`, `time`, `datetime-local`, `datetime`:
|
|
@@ -12162,8 +12167,9 @@ defineInteractionDetector({
|
|
|
12162
12167
|
name: "surface",
|
|
12163
12168
|
claims: (type) =>
|
|
12164
12169
|
type === PAN || type === ZOOM || type === GRAB || type === RELEASE,
|
|
12165
|
-
// A press on the surface may be a tap, a hold or the
|
|
12166
|
-
// nothing may read it as the first until the pointer has said
|
|
12170
|
+
// A press on the surface may be a tap, a hold or the first finger of a pan or
|
|
12171
|
+
// a pinch, and nothing may read it as the first until the pointer has said
|
|
12172
|
+
// which.
|
|
12167
12173
|
disputesPress: true,
|
|
12168
12174
|
setup: (element, trigger, { types, readConfig }) => {
|
|
12169
12175
|
const canPan = types.includes(PAN);
|
|
@@ -15166,21 +15172,33 @@ const getActionResultProperties = (action) => {
|
|
|
15166
15172
|
return actionResultPropertiesMap.get(action);
|
|
15167
15173
|
};
|
|
15168
15174
|
|
|
15169
|
-
// PUT/PATCH results update the UI through the store, and DELETE resets the GET
|
|
15170
|
-
// instead of rerunning it, so a GET rerun would only ever cost a request.
|
|
15171
|
-
// A POST is the one case the client cannot decide alone: whether a new item
|
|
15172
|
-
// belongs to a list depends on filters/pagination the backend owns.
|
|
15173
15175
|
// Rationale in full, plus when to override: docs/list_refresh.md
|
|
15174
15176
|
const defaultRerunOn = {
|
|
15177
|
+
// PUT/PATCH results update the UI through the store, and DELETE resets the
|
|
15178
|
+
// GET rather than rerunning it, so a GET rerun would only ever cost a request.
|
|
15175
15179
|
GET: false,
|
|
15180
|
+
// A POST is the one case the client cannot decide alone: whether a new item
|
|
15181
|
+
// belongs to a list depends on filters/pagination the backend owns.
|
|
15176
15182
|
GET_MANY: ["POST"],
|
|
15183
|
+
// DELETE is in there, unlike for GET_MANY: an action holds ids and the store
|
|
15184
|
+
// drops the deleted one out of every list holding it, while a list reading
|
|
15185
|
+
// by slices holds places — the row that left takes the ones after it one
|
|
15186
|
+
// rank up, and only the collection knows who fills the last one.
|
|
15187
|
+
GET_RANGE: ["POST", "DELETE"],
|
|
15188
|
+
};
|
|
15189
|
+
// A key a `rerunOn` leaves out keeps the value it would have had without it:
|
|
15190
|
+
// the default for a resource, the value of the resource it is declared on for
|
|
15191
|
+
// a withParams() scope or a relation.
|
|
15192
|
+
const resolveRerunOn = (rerunOn, inheritedRerunOn = defaultRerunOn) => {
|
|
15193
|
+
if (!rerunOn) {
|
|
15194
|
+
return inheritedRerunOn;
|
|
15195
|
+
}
|
|
15196
|
+
return {
|
|
15197
|
+
GET: rerunOn.GET ?? inheritedRerunOn.GET,
|
|
15198
|
+
GET_MANY: rerunOn.GET_MANY ?? inheritedRerunOn.GET_MANY,
|
|
15199
|
+
GET_RANGE: rerunOn.GET_RANGE ?? inheritedRerunOn.GET_RANGE,
|
|
15200
|
+
};
|
|
15177
15201
|
};
|
|
15178
|
-
// What makes a range reader stale (rerunOn.GET_RANGE overrides it). DELETE is
|
|
15179
|
-
// in there, unlike for GET_MANY: an action holds ids and the store drops the
|
|
15180
|
-
// deleted one out of every list holding it, while a list reading by slices
|
|
15181
|
-
// holds places — the row that left takes the ones after it one rank up, and
|
|
15182
|
-
// only the collection knows who fills the last one.
|
|
15183
|
-
const defaultInvalidateRangeOn = ["POST", "DELETE"];
|
|
15184
15202
|
|
|
15185
15203
|
// This handles ALL resource lifecycle logic (rerun/reset) across all resources
|
|
15186
15204
|
const createResourceLifecycleManager = () => {
|
|
@@ -15190,7 +15208,7 @@ const createResourceLifecycleManager = () => {
|
|
|
15190
15208
|
|
|
15191
15209
|
const registerResource = (resourceScope, config) => {
|
|
15192
15210
|
const {
|
|
15193
|
-
rerunOn
|
|
15211
|
+
rerunOn,
|
|
15194
15212
|
paramScope = null,
|
|
15195
15213
|
dependencies = [],
|
|
15196
15214
|
uniqueKeys = [],
|
|
@@ -15473,8 +15491,7 @@ const createResourceLifecycleManager = () => {
|
|
|
15473
15491
|
if (!isSameResource && !isDependent) {
|
|
15474
15492
|
continue;
|
|
15475
15493
|
}
|
|
15476
|
-
|
|
15477
|
-
if (!shouldRerunAfter(invalidateOn, triggerVerb)) {
|
|
15494
|
+
if (!shouldRerunAfter(config.rerunOn.GET_RANGE, triggerVerb)) {
|
|
15478
15495
|
continue;
|
|
15479
15496
|
}
|
|
15480
15497
|
for (const rangeReader of config.rangeReaderSet) {
|
|
@@ -15975,7 +15992,7 @@ const resource = (
|
|
|
15975
15992
|
addItemSetup,
|
|
15976
15993
|
createRestAction: createRestActionForRoot,
|
|
15977
15994
|
paramScope: getParamScope(undefined),
|
|
15978
|
-
rerunOn,
|
|
15995
|
+
rerunOn: resolveRerunOn(rerunOn),
|
|
15979
15996
|
dependencies,
|
|
15980
15997
|
});
|
|
15981
15998
|
};
|
|
@@ -15993,6 +16010,15 @@ const createResource = (
|
|
|
15993
16010
|
paramScope,
|
|
15994
16011
|
rerunOn,
|
|
15995
16012
|
dependencies,
|
|
16013
|
+
// Set on a scoped child: the resource owning each of its items, which
|
|
16014
|
+
// live in one store per owner (`store` is the owner's).
|
|
16015
|
+
ownerName,
|
|
16016
|
+
// Set on a scopedMany child: its per-owner store looked up by owner key,
|
|
16017
|
+
// and the owner whose GET embeds the collection. Options rather than
|
|
16018
|
+
// properties added afterwards, so every facade of the relation — the one
|
|
16019
|
+
// scopedMany returns and each withParams() of it — carries both.
|
|
16020
|
+
getChildStore,
|
|
16021
|
+
ownerDependency,
|
|
15996
16022
|
},
|
|
15997
16023
|
) => {
|
|
15998
16024
|
const params = paramScope.params;
|
|
@@ -16018,6 +16044,8 @@ const createResource = (
|
|
|
16018
16044
|
// private but exposed for convenience
|
|
16019
16045
|
store,
|
|
16020
16046
|
addItemSetup,
|
|
16047
|
+
ownerName,
|
|
16048
|
+
getChildStore,
|
|
16021
16049
|
};
|
|
16022
16050
|
|
|
16023
16051
|
resourceLifecycleManager.registerResource(stateFacade, {
|
|
@@ -16026,6 +16054,17 @@ const createResource = (
|
|
|
16026
16054
|
dependencies,
|
|
16027
16055
|
uniqueKeys,
|
|
16028
16056
|
});
|
|
16057
|
+
if (ownerDependency) {
|
|
16058
|
+
// A child collection mutated (POST etc.) makes the owner GET re-fetch:
|
|
16059
|
+
// the owner embeds the child array and only the backend knows the new
|
|
16060
|
+
// ordering. Registered per facade, since the rerun is looked up by the
|
|
16061
|
+
// facade whose action completed.
|
|
16062
|
+
resourceLifecycleManager.addDependency(
|
|
16063
|
+
stateFacade,
|
|
16064
|
+
ownerDependency.owner,
|
|
16065
|
+
ownerDependency.propertyName,
|
|
16066
|
+
);
|
|
16067
|
+
}
|
|
16029
16068
|
const onActionComplete = (actionCompleted) => {
|
|
16030
16069
|
resourceLifecycleManager.onActionComplete(actionCompleted, {
|
|
16031
16070
|
resourceScope: stateFacade,
|
|
@@ -16038,8 +16077,12 @@ const createResource = (
|
|
|
16038
16077
|
* Actions from parameterized resources only trigger rerun/reset for other actions with
|
|
16039
16078
|
* identical parameters, preventing cross-contamination between different parameter sets.
|
|
16040
16079
|
*
|
|
16080
|
+
* The actions are the resource's own kind: on a relation they keep its callback return
|
|
16081
|
+
* contract and write where it writes (`TABLE_COLUMNS.withParams(…).GET_MANY` returns
|
|
16082
|
+
* `[ownerId, items]` and replaces that owner's collection).
|
|
16083
|
+
*
|
|
16041
16084
|
* @param {Object} params - Parameters to bind to all actions of this resource (required)
|
|
16042
|
-
* @param {{ rerunOn?: Object, dependencies?: Object[] }} [options] - reruns of that scope; left out
|
|
16085
|
+
* @param {{ rerunOn?: Object, dependencies?: Object[] }} [options] - reruns of that scope; a `rerunOn` key left out keeps the resource's value, and `dependencies` left out are the resource's
|
|
16043
16086
|
* @returns {Object} A new resource instance with parameter-bound actions and isolated lifecycle
|
|
16044
16087
|
* @see docs/resource.md — what a scope isolates, and `dependencies`
|
|
16045
16088
|
*
|
|
@@ -16063,7 +16106,6 @@ const createResource = (
|
|
|
16063
16106
|
paramsToInject,
|
|
16064
16107
|
{ dependencies: withParamsDeps, rerunOn: withParamsRerunOn } = {},
|
|
16065
16108
|
) => {
|
|
16066
|
-
const declarationSite = getDeclarationSite();
|
|
16067
16109
|
if (!paramsToInject || Object.keys(paramsToInject).length === 0) {
|
|
16068
16110
|
throw new Error(`resource(${name}).withParams() requires parameters`);
|
|
16069
16111
|
}
|
|
@@ -16071,11 +16113,6 @@ const createResource = (
|
|
|
16071
16113
|
? { ...params, ...paramsToInject }
|
|
16072
16114
|
: paramsToInject;
|
|
16073
16115
|
const resolvedParamScope = getParamScope(resolvedParams);
|
|
16074
|
-
const createRestActionWithParams = createRestActionFactoryForRoot(name, {
|
|
16075
|
-
idKey,
|
|
16076
|
-
store,
|
|
16077
|
-
declarationSite,
|
|
16078
|
-
});
|
|
16079
16116
|
return createResource(name, {
|
|
16080
16117
|
idKey,
|
|
16081
16118
|
uniqueKeys,
|
|
@@ -16083,10 +16120,16 @@ const createResource = (
|
|
|
16083
16120
|
restCallbacks,
|
|
16084
16121
|
store,
|
|
16085
16122
|
addItemSetup,
|
|
16086
|
-
|
|
16123
|
+
// The actions of this resource's kind: a relation applies its results to
|
|
16124
|
+
// its relation (a scoped child to the per-owner stores), never as root
|
|
16125
|
+
// items of `store`.
|
|
16126
|
+
createRestAction,
|
|
16087
16127
|
paramScope: resolvedParamScope,
|
|
16088
|
-
rerunOn: withParamsRerunOn
|
|
16128
|
+
rerunOn: resolveRerunOn(withParamsRerunOn, rerunOn),
|
|
16089
16129
|
dependencies: withParamsDeps ?? dependencies,
|
|
16130
|
+
ownerName,
|
|
16131
|
+
getChildStore,
|
|
16132
|
+
ownerDependency,
|
|
16090
16133
|
});
|
|
16091
16134
|
};
|
|
16092
16135
|
stateFacade.withParams = withParams;
|
|
@@ -16103,14 +16146,15 @@ const createResource = (
|
|
|
16103
16146
|
* Callback return contracts:
|
|
16104
16147
|
* - GET / PUT → the parent object with the relationship nested inside:
|
|
16105
16148
|
* `async ({ id }) => ({ id, session: { id: 10, token: "abc" } })`; `null` for no relationship
|
|
16106
|
-
* - DELETE → the parent id
|
|
16149
|
+
* - DELETE → the parent id, or `{ id }`; the property is set to `null`
|
|
16107
16150
|
*
|
|
16108
16151
|
* The backend may also embed the child inline in a parent GET/POST response — the
|
|
16109
16152
|
* setter on the property upserts the nested object into the child store.
|
|
16110
16153
|
*
|
|
16111
|
-
* Returns the relationship resource
|
|
16112
|
-
*
|
|
16113
|
-
*
|
|
16154
|
+
* Returns the relationship resource, itself chainable. Its items are the child
|
|
16155
|
+
* resource's own, shared by every parent: `USER_SESSION.one("device", DEVICE)`
|
|
16156
|
+
* adds a reactive `.device` property to every session, as
|
|
16157
|
+
* `SESSION.one("device", DEVICE)` would.
|
|
16114
16158
|
*
|
|
16115
16159
|
* @param {string} propertyName - property holding the child on each parent item
|
|
16116
16160
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
@@ -16130,6 +16174,13 @@ const createResource = (
|
|
|
16130
16174
|
} = {},
|
|
16131
16175
|
) => {
|
|
16132
16176
|
const declarationSite = getDeclarationSite();
|
|
16177
|
+
if (ownerName) {
|
|
16178
|
+
refuseCallbacksNamingScopedItem(
|
|
16179
|
+
`${name}.one("${propertyName}")`,
|
|
16180
|
+
{ GET, PUT, DELETE },
|
|
16181
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
16182
|
+
);
|
|
16183
|
+
}
|
|
16133
16184
|
const childName = `${name}.${propertyName}`;
|
|
16134
16185
|
const childIdKey = childResource.idKey;
|
|
16135
16186
|
const childStore = childResource.store;
|
|
@@ -16190,14 +16241,18 @@ const createResource = (
|
|
|
16190
16241
|
);
|
|
16191
16242
|
});
|
|
16192
16243
|
|
|
16193
|
-
const createRestActionForOne = (
|
|
16244
|
+
const createRestActionForOne = (
|
|
16245
|
+
verb,
|
|
16246
|
+
callback,
|
|
16247
|
+
{ onActionComplete, paramScope },
|
|
16248
|
+
) => {
|
|
16194
16249
|
const applyResultToValue =
|
|
16195
16250
|
verb === "DELETE"
|
|
16196
|
-
? (
|
|
16197
|
-
const item = store.select(
|
|
16251
|
+
? (itemIdOrItemProps) => {
|
|
16252
|
+
const item = store.select(itemIdOrItemProps);
|
|
16198
16253
|
const childItemId = item[propertyName][childIdKey];
|
|
16199
16254
|
store.upsert({
|
|
16200
|
-
[idKey]:
|
|
16255
|
+
[idKey]: item[idKey],
|
|
16201
16256
|
[propertyName]: null,
|
|
16202
16257
|
});
|
|
16203
16258
|
return childItemId;
|
|
@@ -16225,7 +16280,7 @@ const createResource = (
|
|
|
16225
16280
|
if (!isProps(result) && !primitiveCanBeId(result)) {
|
|
16226
16281
|
throwInvalidResult(
|
|
16227
16282
|
action.name,
|
|
16228
|
-
`
|
|
16283
|
+
`the "${name}" id, or { ${idKey} } (the item whose "${propertyName}" becomes null)`,
|
|
16229
16284
|
result,
|
|
16230
16285
|
);
|
|
16231
16286
|
}
|
|
@@ -16250,11 +16305,11 @@ const createResource = (
|
|
|
16250
16305
|
PUT,
|
|
16251
16306
|
DELETE,
|
|
16252
16307
|
},
|
|
16253
|
-
store,
|
|
16254
|
-
addItemSetup,
|
|
16308
|
+
store: childStore,
|
|
16309
|
+
addItemSetup: childResource.addItemSetup,
|
|
16255
16310
|
createRestAction: createRestActionForOne,
|
|
16256
16311
|
paramScope,
|
|
16257
|
-
rerunOn: oneRerunOn
|
|
16312
|
+
rerunOn: resolveRerunOn(oneRerunOn, rerunOn),
|
|
16258
16313
|
dependencies: oneDependencies ?? dependencies,
|
|
16259
16314
|
});
|
|
16260
16315
|
};
|
|
@@ -16277,6 +16332,9 @@ const createResource = (
|
|
|
16277
16332
|
* - DELETE → `[parentId, childId]`
|
|
16278
16333
|
* - DELETE_MANY → `[parentId, [childId, childId, …]]`
|
|
16279
16334
|
*
|
|
16335
|
+
* Returns the relationship resource, itself chainable; its items are the child
|
|
16336
|
+
* resource's own, as for `.one()`.
|
|
16337
|
+
*
|
|
16280
16338
|
* @param {string} propertyName - property holding the child array on each parent item
|
|
16281
16339
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
16282
16340
|
* @param {Object} [restCallbacks] - `{ rerunOn, dependencies, GET, GET_MANY, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
@@ -16302,6 +16360,13 @@ const createResource = (
|
|
|
16302
16360
|
} = {},
|
|
16303
16361
|
) => {
|
|
16304
16362
|
const declarationSite = getDeclarationSite();
|
|
16363
|
+
if (ownerName) {
|
|
16364
|
+
refuseCallbacksNamingScopedItem(
|
|
16365
|
+
`${name}.many("${propertyName}")`,
|
|
16366
|
+
{ GET_MANY, DELETE, DELETE_MANY },
|
|
16367
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
16368
|
+
);
|
|
16369
|
+
}
|
|
16305
16370
|
const childStore = childResource.store;
|
|
16306
16371
|
const childIdKey = childResource.idKey;
|
|
16307
16372
|
const childName = `${name}.${propertyName}`;
|
|
@@ -16333,21 +16398,23 @@ const createResource = (
|
|
|
16333
16398
|
const createRestActionForMany = (
|
|
16334
16399
|
verb,
|
|
16335
16400
|
callback,
|
|
16336
|
-
{ isMany, onActionComplete },
|
|
16401
|
+
{ isMany, onActionComplete, paramScope },
|
|
16337
16402
|
) => {
|
|
16338
16403
|
if (!isMany) {
|
|
16339
16404
|
return createRestActionAffectingOneItem(verb, callback, {
|
|
16340
16405
|
onActionComplete,
|
|
16406
|
+
paramScope,
|
|
16341
16407
|
});
|
|
16342
16408
|
}
|
|
16343
16409
|
return createRestActionAffectingManyItems(verb, callback, {
|
|
16344
16410
|
onActionComplete,
|
|
16411
|
+
paramScope,
|
|
16345
16412
|
});
|
|
16346
16413
|
};
|
|
16347
16414
|
const createRestActionAffectingOneItem = (
|
|
16348
16415
|
verb,
|
|
16349
16416
|
callback,
|
|
16350
|
-
{ onActionComplete },
|
|
16417
|
+
{ onActionComplete, paramScope },
|
|
16351
16418
|
) => {
|
|
16352
16419
|
const applyResultToValue =
|
|
16353
16420
|
verb === "DELETE"
|
|
@@ -16410,7 +16477,7 @@ const createResource = (
|
|
|
16410
16477
|
const createRestActionAffectingManyItems = (
|
|
16411
16478
|
verb,
|
|
16412
16479
|
callback,
|
|
16413
|
-
{ onActionComplete },
|
|
16480
|
+
{ onActionComplete, paramScope },
|
|
16414
16481
|
) => {
|
|
16415
16482
|
const applyResultToValue =
|
|
16416
16483
|
verb === "GET"
|
|
@@ -16512,11 +16579,11 @@ const createResource = (
|
|
|
16512
16579
|
DELETE,
|
|
16513
16580
|
DELETE_MANY,
|
|
16514
16581
|
},
|
|
16515
|
-
store,
|
|
16516
|
-
addItemSetup,
|
|
16582
|
+
store: childStore,
|
|
16583
|
+
addItemSetup: childResource.addItemSetup,
|
|
16517
16584
|
createRestAction: createRestActionForMany,
|
|
16518
16585
|
paramScope,
|
|
16519
|
-
rerunOn: manyRerunOn
|
|
16586
|
+
rerunOn: resolveRerunOn(manyRerunOn, rerunOn),
|
|
16520
16587
|
dependencies: manyDependencies ?? dependencies,
|
|
16521
16588
|
});
|
|
16522
16589
|
};
|
|
@@ -16538,7 +16605,12 @@ const createResource = (
|
|
|
16538
16605
|
* Mutations apply directly to the owner's signal, so the parent GET is never rerun.
|
|
16539
16606
|
*
|
|
16540
16607
|
* Returns the child relationship resource, itself chainable:
|
|
16541
|
-
* `USER_PROFILE.one("theme", THEME)` adds a reactive `.theme` property on each profile
|
|
16608
|
+
* `USER_PROFILE.one("theme", THEME)` adds a reactive `.theme` property on each profile,
|
|
16609
|
+
* fed by what this resource's callbacks embed (`[id, { bio, theme: { id: 3 } }]`). A
|
|
16610
|
+
* profile exists only inside its user, so the chained relation declares none of the
|
|
16611
|
+
* callbacks whose result names a profile (`.one()` GET/PUT/DELETE, `.many()`
|
|
16612
|
+
* GET_MANY/DELETE/DELETE_MANY, any `.scopedOne()`/`.scopedMany()` callback): such a
|
|
16613
|
+
* result cannot say which user holds it, and the declaration throws.
|
|
16542
16614
|
*
|
|
16543
16615
|
* @param {string} propertyName - property holding the sub-object on each owner item
|
|
16544
16616
|
* @param {Object} [restCallbacks] - `{ idKey, rerunOn, dependencies, GET, POST, PUT, PATCH, DELETE }`
|
|
@@ -16558,6 +16630,14 @@ const createResource = (
|
|
|
16558
16630
|
DELETE,
|
|
16559
16631
|
} = {},
|
|
16560
16632
|
) => {
|
|
16633
|
+
const declarationSite = getDeclarationSite();
|
|
16634
|
+
if (ownerName) {
|
|
16635
|
+
refuseCallbacksNamingScopedItem(
|
|
16636
|
+
`${name}.scopedOne("${propertyName}")`,
|
|
16637
|
+
{ GET, POST, PUT, PATCH, DELETE },
|
|
16638
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
16639
|
+
);
|
|
16640
|
+
}
|
|
16561
16641
|
const childName = `${name}.${propertyName}`;
|
|
16562
16642
|
|
|
16563
16643
|
// Callbacks added by chained .one()/.many() on the child resource,
|
|
@@ -16587,7 +16667,12 @@ const createResource = (
|
|
|
16587
16667
|
childSignal.value = childItem; // first activation: null → childItem
|
|
16588
16668
|
}
|
|
16589
16669
|
};
|
|
16590
|
-
|
|
16670
|
+
if (!ownerName) {
|
|
16671
|
+
// Declared on a scoped child, an owner id is only unique inside its
|
|
16672
|
+
// own owner and no callback addresses it (see
|
|
16673
|
+
// refuseCallbacksNamingScopedItem): nothing is looked up by it.
|
|
16674
|
+
applyPropsMap.set(ownerId, applyProps);
|
|
16675
|
+
}
|
|
16591
16676
|
|
|
16592
16677
|
applyProps(ownerItem[propertyName]);
|
|
16593
16678
|
|
|
@@ -16599,7 +16684,7 @@ const createResource = (
|
|
|
16599
16684
|
const createRestActionForScopedOne = (
|
|
16600
16685
|
verb,
|
|
16601
16686
|
callback,
|
|
16602
|
-
{ onActionComplete },
|
|
16687
|
+
{ onActionComplete, paramScope },
|
|
16603
16688
|
) => {
|
|
16604
16689
|
const childActionName = `${childName}.${verb}`;
|
|
16605
16690
|
return createAction(callback, {
|
|
@@ -16645,8 +16730,9 @@ const createResource = (
|
|
|
16645
16730
|
addItemSetup: childAddItemSetup,
|
|
16646
16731
|
createRestAction: createRestActionForScopedOne,
|
|
16647
16732
|
paramScope,
|
|
16648
|
-
rerunOn: scopedOneRerunOn
|
|
16733
|
+
rerunOn: resolveRerunOn(scopedOneRerunOn, rerunOn),
|
|
16649
16734
|
dependencies: scopedOneDependencies ?? dependencies,
|
|
16735
|
+
ownerName: name,
|
|
16650
16736
|
});
|
|
16651
16737
|
};
|
|
16652
16738
|
|
|
@@ -16674,7 +16760,14 @@ const createResource = (
|
|
|
16674
16760
|
* `propertyName`), and the child's own GET_MANY reruns per its `rerunOn`.
|
|
16675
16761
|
*
|
|
16676
16762
|
* Returns the child relationship resource, itself chainable:
|
|
16677
|
-
* `TABLE_COLUMNS.one("dataType", DATA_TYPE)` adds a reactive `.dataType` property on each column
|
|
16763
|
+
* `TABLE_COLUMNS.one("dataType", DATA_TYPE)` adds a reactive `.dataType` property on each column,
|
|
16764
|
+
* fed by what this resource's callbacks embed (`[id, [{ name, dataType: { id: 2 } }]]`). A
|
|
16765
|
+
* column exists only inside its table — two tables can each have an `email` — so the chained
|
|
16766
|
+
* relation declares none of the callbacks whose result names a column (`.one()`
|
|
16767
|
+
* GET/PUT/DELETE, `.many()` GET_MANY/DELETE/DELETE_MANY, any `.scopedOne()`/`.scopedMany()`
|
|
16768
|
+
* callback): such a result cannot say which table holds it, and the declaration throws.
|
|
16769
|
+
* A `.scopedMany()` chained on it keeps one collection per column: `users.email` and
|
|
16770
|
+
* `admins.email` never share one.
|
|
16678
16771
|
*
|
|
16679
16772
|
* @param {string} propertyName - property holding the collection on each owner item
|
|
16680
16773
|
* @param {Object} [restCallbacks] - `{ idKey, rerunOn, dependencies, GET, GET_MANY, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
@@ -16699,6 +16792,25 @@ const createResource = (
|
|
|
16699
16792
|
DELETE_MANY,
|
|
16700
16793
|
} = {},
|
|
16701
16794
|
) => {
|
|
16795
|
+
const declarationSite = getDeclarationSite();
|
|
16796
|
+
if (ownerName) {
|
|
16797
|
+
refuseCallbacksNamingScopedItem(
|
|
16798
|
+
`${name}.scopedMany("${propertyName}")`,
|
|
16799
|
+
{
|
|
16800
|
+
GET,
|
|
16801
|
+
GET_MANY,
|
|
16802
|
+
POST,
|
|
16803
|
+
POST_MANY,
|
|
16804
|
+
PUT,
|
|
16805
|
+
PUT_MANY,
|
|
16806
|
+
PATCH,
|
|
16807
|
+
PATCH_MANY,
|
|
16808
|
+
DELETE,
|
|
16809
|
+
DELETE_MANY,
|
|
16810
|
+
},
|
|
16811
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
16812
|
+
);
|
|
16813
|
+
}
|
|
16702
16814
|
const childName = `${name}.${propertyName}`;
|
|
16703
16815
|
|
|
16704
16816
|
// Callbacks added by chained .one()/.many() on the child resource,
|
|
@@ -16720,37 +16832,42 @@ const createResource = (
|
|
|
16720
16832
|
return childItem;
|
|
16721
16833
|
},
|
|
16722
16834
|
});
|
|
16723
|
-
|
|
16724
|
-
scopeMap.set(ownerKey, scope);
|
|
16725
|
-
return scope;
|
|
16835
|
+
return { childStore, idArraySignal: signal([]) };
|
|
16726
16836
|
};
|
|
16727
16837
|
addItemSetup((item) => {
|
|
16728
16838
|
const ownerId = item[idKey];
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16839
|
+
let scope;
|
|
16840
|
+
if (ownerName) {
|
|
16841
|
+
// The owner is itself a scoped child: its id is only unique inside its
|
|
16842
|
+
// own owner, so the scope is the item's alone, never looked up by id
|
|
16843
|
+
// (no callback addresses it, see refuseCallbacksNamingScopedItem).
|
|
16844
|
+
scope = createScope(ownerId);
|
|
16845
|
+
} else {
|
|
16846
|
+
// Reuse an existing scope if one was already created under a uniqueKey
|
|
16847
|
+
// value (e.g. rows were fetched by tablename before the full table was loaded).
|
|
16848
|
+
scope = scopeMap.get(ownerId);
|
|
16849
|
+
if (!scope) {
|
|
16850
|
+
for (const uniqueKey of uniqueKeys) {
|
|
16851
|
+
const uniqueKeyValue = item[uniqueKey];
|
|
16852
|
+
if (uniqueKeyValue !== undefined && scopeMap.has(uniqueKeyValue)) {
|
|
16853
|
+
scope = scopeMap.get(uniqueKeyValue);
|
|
16854
|
+
break;
|
|
16855
|
+
}
|
|
16856
|
+
}
|
|
16857
|
+
}
|
|
16858
|
+
if (!scope) {
|
|
16859
|
+
scope = createScope(ownerId);
|
|
16860
|
+
}
|
|
16861
|
+
// Register the scope under the id and every uniqueKey value so that
|
|
16862
|
+
// resolveOwnerId can address it whichever key a callback returns.
|
|
16863
|
+
scopeMap.set(ownerId, scope);
|
|
16734
16864
|
for (const uniqueKey of uniqueKeys) {
|
|
16735
16865
|
const uniqueKeyValue = item[uniqueKey];
|
|
16736
|
-
if (uniqueKeyValue !== undefined
|
|
16737
|
-
|
|
16738
|
-
break;
|
|
16866
|
+
if (uniqueKeyValue !== undefined) {
|
|
16867
|
+
scopeMap.set(uniqueKeyValue, scope);
|
|
16739
16868
|
}
|
|
16740
16869
|
}
|
|
16741
16870
|
}
|
|
16742
|
-
if (!scope) {
|
|
16743
|
-
scope = createScope(ownerId);
|
|
16744
|
-
}
|
|
16745
|
-
// Register the scope under the id and every uniqueKey value so that
|
|
16746
|
-
// resolveOwnerId can address it whichever key a callback returns.
|
|
16747
|
-
scopeMap.set(ownerId, scope);
|
|
16748
|
-
for (const uniqueKey of uniqueKeys) {
|
|
16749
|
-
const uniqueKeyValue = item[uniqueKey];
|
|
16750
|
-
if (uniqueKeyValue !== undefined) {
|
|
16751
|
-
scopeMap.set(uniqueKeyValue, scope);
|
|
16752
|
-
}
|
|
16753
|
-
}
|
|
16754
16871
|
|
|
16755
16872
|
const { childStore, idArraySignal } = scope;
|
|
16756
16873
|
const updateChildItemIdArray = createChildIdArrayUpdater(
|
|
@@ -16788,7 +16905,7 @@ const createResource = (
|
|
|
16788
16905
|
const createRestActionForScopedMany = (
|
|
16789
16906
|
verb,
|
|
16790
16907
|
callback,
|
|
16791
|
-
{ isMany, onActionComplete },
|
|
16908
|
+
{ isMany, onActionComplete, paramScope },
|
|
16792
16909
|
) => {
|
|
16793
16910
|
const childActionName = `${childName}.${verb}`;
|
|
16794
16911
|
return createAction(callback, {
|
|
@@ -16810,7 +16927,11 @@ const createResource = (
|
|
|
16810
16927
|
);
|
|
16811
16928
|
// Owner not in store yet: create the scope so actions can run before
|
|
16812
16929
|
// the parent item has been loaded (e.g. rows fetched before their table).
|
|
16813
|
-
|
|
16930
|
+
let scope = scopeMap.get(ownerId);
|
|
16931
|
+
if (!scope) {
|
|
16932
|
+
scope = createScope(ownerId);
|
|
16933
|
+
scopeMap.set(ownerId, scope);
|
|
16934
|
+
}
|
|
16814
16935
|
const { childStore, idArraySignal } = scope;
|
|
16815
16936
|
|
|
16816
16937
|
if (verb === "DELETE") {
|
|
@@ -16880,20 +17001,19 @@ const createResource = (
|
|
|
16880
17001
|
addItemSetup: childAddItemSetup,
|
|
16881
17002
|
createRestAction: createRestActionForScopedMany,
|
|
16882
17003
|
paramScope,
|
|
16883
|
-
rerunOn: scopedManyRerunOn
|
|
17004
|
+
rerunOn: resolveRerunOn(scopedManyRerunOn, rerunOn),
|
|
16884
17005
|
dependencies: scopedManyDependencies ?? dependencies,
|
|
17006
|
+
ownerName: name,
|
|
17007
|
+
// Declared on a scoped child, the scopes are kept per item and never
|
|
17008
|
+
// registered by id (see addItemSetup above): there is no owner key to
|
|
17009
|
+
// find one by.
|
|
17010
|
+
getChildStore: ownerName
|
|
17011
|
+
? undefined
|
|
17012
|
+
: (ownerKey) => scopeMap.get(ownerKey)?.childStore,
|
|
17013
|
+
// scopedOne needs no such dependency: its mutation result carries the
|
|
17014
|
+
// updated object directly.
|
|
17015
|
+
ownerDependency: { owner: stateFacade, propertyName },
|
|
16885
17016
|
});
|
|
16886
|
-
// When a scoped child collection is mutated (POST etc.), the parent GET must
|
|
16887
|
-
// re-fetch: the parent embeds the child array and only the backend knows the
|
|
16888
|
-
// new ordering. (scopedOne does not need this: the mutation result contains
|
|
16889
|
-
// the updated object directly.)
|
|
16890
|
-
resourceLifecycleManager.addDependency(
|
|
16891
|
-
childResource,
|
|
16892
|
-
stateFacade,
|
|
16893
|
-
propertyName,
|
|
16894
|
-
);
|
|
16895
|
-
childResource.getChildStore = (ownerKey) =>
|
|
16896
|
-
scopeMap.get(ownerKey)?.childStore;
|
|
16897
17017
|
return childResource;
|
|
16898
17018
|
};
|
|
16899
17019
|
|
|
@@ -17114,7 +17234,7 @@ const getLastGetItemId = (action) => {
|
|
|
17114
17234
|
};
|
|
17115
17235
|
|
|
17116
17236
|
// Captures the "file:line:column" of the user code that invoked the public
|
|
17117
|
-
// function (resource(), .one(), .many(),
|
|
17237
|
+
// function (resource(), .one(), .many(), .scopedOne(), …), so invalid-result
|
|
17118
17238
|
// errors can point at where the callbacks were declared, not at this file.
|
|
17119
17239
|
// Must be called directly from the public function: the stack offset accounts
|
|
17120
17240
|
// for exactly two frames (getCallerInfo → getDeclarationSite → public fn → user code).
|
|
@@ -17135,6 +17255,36 @@ ${originalActionName} source location: ${declarationSite}`,
|
|
|
17135
17255
|
};
|
|
17136
17256
|
};
|
|
17137
17257
|
|
|
17258
|
+
// A scoped child's item exists only inside its owner: two owners can each hold
|
|
17259
|
+
// one with the same id. The callbacks of a relation chained on it whose result
|
|
17260
|
+
// names that item do so by its id alone — `.one()`/`.many()` as the parent
|
|
17261
|
+
// object or id, `.scopedOne()`/`.scopedMany()` as the `ownerId` of
|
|
17262
|
+
// `[ownerId, …]` — so applied they would write the owner's store (`store` of a
|
|
17263
|
+
// scoped child) or the scope of another owner's item. They are refused rather
|
|
17264
|
+
// than routed: routing needs the owner in the result, a contract none of them
|
|
17265
|
+
// has.
|
|
17266
|
+
const refuseCallbacksNamingScopedItem = (
|
|
17267
|
+
relationLabel,
|
|
17268
|
+
callbacks,
|
|
17269
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
17270
|
+
) => {
|
|
17271
|
+
const declaredKeys = [];
|
|
17272
|
+
for (const key of Object.keys(callbacks)) {
|
|
17273
|
+
if (callbacks[key]) {
|
|
17274
|
+
declaredKeys.push(key);
|
|
17275
|
+
}
|
|
17276
|
+
}
|
|
17277
|
+
if (declaredKeys.length === 0) {
|
|
17278
|
+
return;
|
|
17279
|
+
}
|
|
17280
|
+
const declared = declaredKeys.join(", ");
|
|
17281
|
+
throw new Error(
|
|
17282
|
+
`${relationLabel} cannot declare ${declared}: a "${name}" item exists only inside its "${ownerName}", and the result of ${declared} names it without saying which "${ownerName}" holds it.
|
|
17283
|
+
Return "${propertyName}" embedded in what the "${name}" callbacks return ([ownerId, { …, ${propertyName} }]): those say which "${ownerName}".
|
|
17284
|
+
${relationLabel} source location: ${declarationSite}`,
|
|
17285
|
+
);
|
|
17286
|
+
};
|
|
17287
|
+
|
|
17138
17288
|
// Shared by .many() and .scopedMany(): converts a raw relationship value (an
|
|
17139
17289
|
// array of child props/ids, or anything else meaning "empty") into an array of
|
|
17140
17290
|
// child ids, upserting each entry into the child store. The id array signal is
|
|
@@ -17276,22 +17426,28 @@ Received an object with keys: ${keys.join(", ")}.`,
|
|
|
17276
17426
|
* // which in turn triggers the route Signal->URL sync and updates the browser URL.
|
|
17277
17427
|
*/
|
|
17278
17428
|
const syncResourceToSignals = (resource, propertyToSignalMap) => {
|
|
17279
|
-
if (resource.
|
|
17429
|
+
if (resource.ownerName) {
|
|
17430
|
+
// A scoped child's `store` is its owner's: watching it would sync the
|
|
17431
|
+
// owner items' properties, not the child's.
|
|
17280
17432
|
throw new Error(
|
|
17281
|
-
`syncResourceToSignals: "${resource.name}"
|
|
17433
|
+
`syncResourceToSignals: "${resource.name}" lives inside each "${resource.ownerName}", so it has no store of its own to watch.${resource.getChildStore ? " Use syncOwnedResourceToSignals instead." : ""}`,
|
|
17282
17434
|
);
|
|
17283
17435
|
}
|
|
17284
17436
|
syncStoreToSignals(resource.store, propertyToSignalMap);
|
|
17285
17437
|
};
|
|
17286
17438
|
|
|
17287
17439
|
/**
|
|
17288
|
-
* The same, for a `
|
|
17289
|
-
* per owner, so the store to watch is the one of the owner
|
|
17290
|
-
* (by id or by any unique key), and it is switched when the
|
|
17291
|
-
* Nothing is synced while `ownerSignal` holds `null`/`undefined`
|
|
17292
|
-
* owner nothing has been loaded for yet.
|
|
17440
|
+
* The same, for a `scopedMany` resource (or a `withParams()` of one): its items
|
|
17441
|
+
* live in one store per owner, so the store to watch is the one of the owner
|
|
17442
|
+
* `ownerSignal` names (by id or by any unique key), and it is switched when the
|
|
17443
|
+
* signal changes. Nothing is synced while `ownerSignal` holds `null`/`undefined`
|
|
17444
|
+
* or names an owner nothing has been loaded for yet.
|
|
17293
17445
|
*
|
|
17294
|
-
*
|
|
17446
|
+
* A `scopedOne` child has no store (each owner holds its one item), and a
|
|
17447
|
+
* `scopedMany` declared on a scoped child is kept per item with no owner key to
|
|
17448
|
+
* find it by: both are refused.
|
|
17449
|
+
*
|
|
17450
|
+
* @param {Object} resource - a resource made by `.scopedMany()`
|
|
17295
17451
|
* @param {import("@preact/signals").Signal} ownerSignal - the owner whose children are watched
|
|
17296
17452
|
* @param {Object} propertyToSignalMap - `{ [propertyName]: signal }`, as for `syncResourceToSignals`
|
|
17297
17453
|
*/
|
|
@@ -17301,8 +17457,13 @@ const syncOwnedResourceToSignals = (
|
|
|
17301
17457
|
propertyToSignalMap,
|
|
17302
17458
|
) => {
|
|
17303
17459
|
if (!resource.getChildStore) {
|
|
17460
|
+
if (!resource.ownerName) {
|
|
17461
|
+
throw new Error(
|
|
17462
|
+
`syncOwnedResourceToSignals: "${resource.name}" is not a scoped resource. Use syncResourceToSignals instead.`,
|
|
17463
|
+
);
|
|
17464
|
+
}
|
|
17304
17465
|
throw new Error(
|
|
17305
|
-
`syncOwnedResourceToSignals: "${resource.name}" is
|
|
17466
|
+
`syncOwnedResourceToSignals: "${resource.name}" has no store per "${resource.ownerName}" to watch: a scopedOne child is held by each owner, and a scopedMany declared on a scoped child has no owner key to find its store by.`,
|
|
17306
17467
|
);
|
|
17307
17468
|
}
|
|
17308
17469
|
effect(() => {
|
|
@@ -19592,9 +19753,9 @@ const isSizeSpacingKey = (key) => {
|
|
|
19592
19753
|
// "vvw"/"vvh" are navi's own: the *visual* viewport, which — unlike vw/dvw —
|
|
19593
19754
|
// shrinks when the mobile virtual keyboard opens (see layout/responsive.js), so
|
|
19594
19755
|
// they are what a popup meant to stay clear of the keyboard should use.
|
|
19595
|
-
// "appw"/"apph" are the same thing narrowed to the app's own screen:
|
|
19596
|
-
//
|
|
19597
|
-
//
|
|
19756
|
+
// "appw"/"apph" are the same thing narrowed to the app's own screen: the
|
|
19757
|
+
// visual viewport minus the app's bands (--navi-app-inset-*), so identical to
|
|
19758
|
+
// vvw/vvh until the app declares bands. A gap meant to read as "a small margin" must use these —
|
|
19598
19759
|
// 3vvw on a 1500px window is a 45px gap around a 600px app.
|
|
19599
19760
|
// Functions rather than the signals themselves: appw/apph are not a signal to
|
|
19600
19761
|
// read but a value to compute (a signal, then a CSS var read back). Reading the
|
|
@@ -29288,7 +29449,7 @@ const TRANSITION_FURNITURE_CSS = /* css */ `.navi_fixed_bar[data-navi-transition
|
|
|
29288
29449
|
clip-path: var(--wall-holes-arriving, var(--wall-holes));
|
|
29289
29450
|
}
|
|
29290
29451
|
|
|
29291
|
-
:root[data-navi-route-transition] {
|
|
29452
|
+
:root[data-navi-route-transition-target="area"] {
|
|
29292
29453
|
& .navi_dialog::backdrop, & .navi_popover_backdrop {
|
|
29293
29454
|
backdrop-filter: none;
|
|
29294
29455
|
background: none;
|
|
@@ -29296,8 +29457,8 @@ const TRANSITION_FURNITURE_CSS = /* css */ `.navi_fixed_bar[data-navi-transition
|
|
|
29296
29457
|
}
|
|
29297
29458
|
`;
|
|
29298
29459
|
|
|
29299
|
-
// Called
|
|
29300
|
-
//
|
|
29460
|
+
// Called as a movement starts over a marked area, never at module scope: a
|
|
29461
|
+
// page that never travels between routes must not carry this sheet, and a
|
|
29301
29462
|
// build that sees no caller drops the css with the function.
|
|
29302
29463
|
const installTransitionFurnitureCss = () => {
|
|
29303
29464
|
import.meta.css = [TRANSITION_FURNITURE_CSS, "@jsenv/navi/src/nav/transition_furniture.js"];
|
|
@@ -29433,10 +29594,7 @@ const nameFurnitureAround = (areaElement) => {
|
|
|
29433
29594
|
if (!name) {
|
|
29434
29595
|
// A name the application wrote itself answers for that element, and it
|
|
29435
29596
|
// is saying something navi is not: it wants that bar moved on the pages'
|
|
29436
|
-
// clock, by its own CSS.
|
|
29437
|
-
// bar wearing one of ITS names is one this movement inherited from the
|
|
29438
|
-
// one it interrupted, and taking it for the application's would leave it
|
|
29439
|
-
// named for the rest of the document's life.
|
|
29597
|
+
// clock, by its own CSS.
|
|
29440
29598
|
if (getComputedStyle(element).viewTransitionName !== "none") {
|
|
29441
29599
|
continue;
|
|
29442
29600
|
}
|
|
@@ -29760,9 +29918,10 @@ const TRANSITION_WINDOW_CSS = /* css */ `@property --navi-transition-cover-top {
|
|
|
29760
29918
|
}
|
|
29761
29919
|
`;
|
|
29762
29920
|
|
|
29763
|
-
// Called
|
|
29764
|
-
// a page that never travels between routes must not
|
|
29765
|
-
// build that sees no caller drops the css with the
|
|
29921
|
+
// Called by whatever needs the window — a render, or a movement starting —
|
|
29922
|
+
// never at module scope: a page that never travels between routes must not
|
|
29923
|
+
// carry this sheet, and a build that sees no caller drops the css with the
|
|
29924
|
+
// function.
|
|
29766
29925
|
const installTransitionWindowCss = () => {
|
|
29767
29926
|
import.meta.css = [TRANSITION_WINDOW_CSS, "@jsenv/navi/src/nav/transition_window.js"];
|
|
29768
29927
|
};
|
|
@@ -30575,9 +30734,6 @@ const RouteTransitionArea = ({
|
|
|
30575
30734
|
children,
|
|
30576
30735
|
...rest
|
|
30577
30736
|
}) => {
|
|
30578
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
30579
|
-
installTransitionWindowCss();
|
|
30580
|
-
installTransitionFurnitureCss();
|
|
30581
30737
|
const props = {
|
|
30582
30738
|
...rest,
|
|
30583
30739
|
[TRANSITION_AREA_ATTRIBUTE]: ""
|
|
@@ -30646,8 +30802,6 @@ const RouteTransitionArea = ({
|
|
|
30646
30802
|
* @returns {() => void} remove this relation.
|
|
30647
30803
|
*/
|
|
30648
30804
|
const defineRouteTransition = (from, to, transition) => {
|
|
30649
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
30650
|
-
installTransitionWindowCss();
|
|
30651
30805
|
if (!to) {
|
|
30652
30806
|
throw new TypeError(`defineRouteTransition needs a destination: "to" is ${to}. The page reached from anywhere is written defineRouteTransition(null, THAT_PAGE, ...) — there is no relation the other way round, a page LEFT for anywhere being the back half of that one.`);
|
|
30653
30807
|
}
|
|
@@ -30687,8 +30841,6 @@ const defineRouteTransition = (from, to, transition) => {
|
|
|
30687
30841
|
* @returns {() => void} remove this default.
|
|
30688
30842
|
*/
|
|
30689
30843
|
const defineRouteDefaultTransition = transition => {
|
|
30690
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
30691
|
-
installTransitionWindowCss();
|
|
30692
30844
|
const value = normalizeTransition(transition);
|
|
30693
30845
|
defaultTransition = value;
|
|
30694
30846
|
return () => {
|
|
@@ -31264,12 +31416,25 @@ const beginTransition = ({
|
|
|
31264
31416
|
url: absoluteUrl(url),
|
|
31265
31417
|
decision: navigationDecision,
|
|
31266
31418
|
walkHome: null,
|
|
31267
|
-
releaseReverting: null
|
|
31419
|
+
releaseReverting: null,
|
|
31420
|
+
restoreDuration: null
|
|
31268
31421
|
};
|
|
31422
|
+
// The document wears one transition at a time, and the one this interrupts
|
|
31423
|
+
// ends only after this one has begun — too late to take off what it wears
|
|
31424
|
+
// (see end). Taking over therefore starts by taking it off, so that what is
|
|
31425
|
+
// put on below describes this transition alone, including what it leaves
|
|
31426
|
+
// unset: a type, an area, a duration.
|
|
31427
|
+
if (currentTransition) {
|
|
31428
|
+
releaseTransitionRoot(currentTransition);
|
|
31429
|
+
}
|
|
31269
31430
|
currentTransition = transition;
|
|
31270
31431
|
// Said before the picture is taken: whoever names something for a movement
|
|
31271
31432
|
// between two pages decides on it now (see transition_destination.js).
|
|
31272
31433
|
holdTransitionDestination(transition, url);
|
|
31434
|
+
// Adopted by what starts a movement rather than by what declares one: a link
|
|
31435
|
+
// or a navTo() asks for a movement in an app that declared none, and an app
|
|
31436
|
+
// that never moves never carries the sheet.
|
|
31437
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
31273
31438
|
documentElement.setAttribute(TRANSITION_ATTRIBUTE, direction);
|
|
31274
31439
|
if (type) {
|
|
31275
31440
|
documentElement.setAttribute(TRANSITION_TYPE_ATTRIBUTE, type);
|
|
@@ -31291,6 +31456,10 @@ const beginTransition = ({
|
|
|
31291
31456
|
// left (see transition_window.js).
|
|
31292
31457
|
let areaStateBefore = null;
|
|
31293
31458
|
if (areaElement) {
|
|
31459
|
+
// The area is the application's element as often as <RouteTransitionArea>,
|
|
31460
|
+
// so what a movement on it plays with is adopted here, where it is found.
|
|
31461
|
+
installTransitionWindowCss();
|
|
31462
|
+
installTransitionFurnitureCss();
|
|
31294
31463
|
documentElement.setAttribute(TRANSITION_TARGET_ATTRIBUTE, "area");
|
|
31295
31464
|
// Said before the picture is taken, like every name (see
|
|
31296
31465
|
// transition_furniture.js): what the bars are wearing when the transition
|
|
@@ -31301,11 +31470,10 @@ const beginTransition = ({
|
|
|
31301
31470
|
// A duration of this relation's own, worn for the length of the transition —
|
|
31302
31471
|
// and whatever the application had written inline put back afterwards, not
|
|
31303
31472
|
// erased.
|
|
31304
|
-
let restoreDuration = null;
|
|
31305
31473
|
if (duration !== undefined) {
|
|
31306
31474
|
const durationBefore = documentElement.style.getPropertyValue(TRANSITION_DURATION_PROPERTY);
|
|
31307
31475
|
documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, typeof duration === "number" ? `${duration}ms` : duration);
|
|
31308
|
-
restoreDuration = () => {
|
|
31476
|
+
transition.restoreDuration = () => {
|
|
31309
31477
|
if (durationBefore) {
|
|
31310
31478
|
documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, durationBefore);
|
|
31311
31479
|
} else {
|
|
@@ -31366,8 +31534,11 @@ const beginTransition = ({
|
|
|
31366
31534
|
renderWait.stop();
|
|
31367
31535
|
}
|
|
31368
31536
|
// The page arriving is in the DOM and the transition has not started
|
|
31369
|
-
// playing: the one moment both states of the area can be known.
|
|
31370
|
-
|
|
31537
|
+
// playing: the one moment both states of the area can be known. The
|
|
31538
|
+
// browser runs this callback even for a transition skipped before its
|
|
31539
|
+
// first picture — the next one starting skips it — and a transition
|
|
31540
|
+
// replaced holds nothing: what it wore was taken off at the takeover.
|
|
31541
|
+
if (areaElement && currentTransition === transition) {
|
|
31371
31542
|
holdTransitionFurniture(transition, areaElement);
|
|
31372
31543
|
holdTransitionWindow(transition, areaElement, areaStateBefore);
|
|
31373
31544
|
}
|
|
@@ -31376,8 +31547,9 @@ const beginTransition = ({
|
|
|
31376
31547
|
// Whatever ends it — played out, skipped by another transition starting,
|
|
31377
31548
|
// failed before its callback ever ran — the hold is given back and the
|
|
31378
31549
|
// document is handed back to the application. Both are idempotent, and
|
|
31379
|
-
// the
|
|
31380
|
-
// ending late
|
|
31550
|
+
// the document is handed back only by the LAST transition begun: an
|
|
31551
|
+
// earlier one ending late was taken off by the one that replaced it, and
|
|
31552
|
+
// must not strip what that one is wearing.
|
|
31381
31553
|
renderWait.stop();
|
|
31382
31554
|
releaseRendering();
|
|
31383
31555
|
// The hold a way back took, when it is still standing: the pictures were
|
|
@@ -31389,16 +31561,7 @@ const beginTransition = ({
|
|
|
31389
31561
|
}
|
|
31390
31562
|
if (currentTransition === transition) {
|
|
31391
31563
|
currentTransition = null;
|
|
31392
|
-
|
|
31393
|
-
documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
|
|
31394
|
-
documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
|
|
31395
|
-
releaseTransitionWindow(transition);
|
|
31396
|
-
releaseTransitionDestination(transition);
|
|
31397
|
-
releaseTransitionFurniture(transition);
|
|
31398
|
-
releaseTransitionPress(transition);
|
|
31399
|
-
if (restoreDuration) {
|
|
31400
|
-
restoreDuration();
|
|
31401
|
-
}
|
|
31564
|
+
releaseTransitionRoot(transition);
|
|
31402
31565
|
}
|
|
31403
31566
|
};
|
|
31404
31567
|
transition.viewTransition = viewTransition;
|
|
@@ -31406,6 +31569,22 @@ const beginTransition = ({
|
|
|
31406
31569
|
viewTransition.finished.then(end, end);
|
|
31407
31570
|
};
|
|
31408
31571
|
|
|
31572
|
+
// Everything a transition wears on the document for its length, taken off by
|
|
31573
|
+
// its own end or by the transition taking over from it.
|
|
31574
|
+
const releaseTransitionRoot = transition => {
|
|
31575
|
+
const documentElement = document.documentElement;
|
|
31576
|
+
documentElement.removeAttribute(TRANSITION_ATTRIBUTE);
|
|
31577
|
+
documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
|
|
31578
|
+
documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
|
|
31579
|
+
releaseTransitionWindow(transition);
|
|
31580
|
+
releaseTransitionDestination(transition);
|
|
31581
|
+
releaseTransitionFurniture(transition);
|
|
31582
|
+
releaseTransitionPress(transition);
|
|
31583
|
+
if (transition.restoreDuration) {
|
|
31584
|
+
transition.restoreDuration();
|
|
31585
|
+
}
|
|
31586
|
+
};
|
|
31587
|
+
|
|
31409
31588
|
/**
|
|
31410
31589
|
* The movement playing, turned round rather than replaced: this navigation is
|
|
31411
31590
|
* exactly the way back of the one on screen — the door pressed again while the
|
|
@@ -44279,16 +44458,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
44279
44458
|
border-radius: .2em;
|
|
44280
44459
|
}
|
|
44281
44460
|
|
|
44282
|
-
&[data-capitalize] {
|
|
44283
|
-
text-transform: capitalize;
|
|
44284
|
-
|
|
44285
|
-
& .navi_text_sizer {
|
|
44286
|
-
& .navi_text {
|
|
44287
|
-
display: inline-block;
|
|
44288
|
-
}
|
|
44289
|
-
}
|
|
44290
|
-
}
|
|
44291
|
-
|
|
44292
44461
|
&[data-shrinkwrap] {
|
|
44293
44462
|
display: inline-block;
|
|
44294
44463
|
}
|
|
@@ -44311,12 +44480,6 @@ time.navi_text {
|
|
|
44311
44480
|
}
|
|
44312
44481
|
|
|
44313
44482
|
.navi_text {
|
|
44314
|
-
&[data-capitalize] {
|
|
44315
|
-
&:first-letter, & .navi_text_sizer_placeholder:first-letter, & .navi_text_sizer_overlay:first-letter {
|
|
44316
|
-
text-transform: uppercase;
|
|
44317
|
-
}
|
|
44318
|
-
}
|
|
44319
|
-
|
|
44320
44483
|
& .navi_text_sizer, & .navi_text_sizer_placeholder, & .navi_text_sizer_overlay {
|
|
44321
44484
|
display: inherit;
|
|
44322
44485
|
width: inherit;
|
|
@@ -44764,7 +44927,9 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
44764
44927
|
* text block from being wider than its content when inside a flex/grid container.
|
|
44765
44928
|
*
|
|
44766
44929
|
* @param {boolean} [capitalize]
|
|
44767
|
-
* Uppercases the first letter of the text
|
|
44930
|
+
* Uppercases the first letter of the text, and only that one: "lundi 11 mai"
|
|
44931
|
+
* reads "Lundi 11 mai". Applied to the string the text starts with; a text
|
|
44932
|
+
* starting with an element keeps its first letter as written.
|
|
44768
44933
|
*
|
|
44769
44934
|
* @param {string|[number,number]|[string,string]} [selectRange]
|
|
44770
44935
|
* Selects a portion of the text on mount. Pass a substring to search for, a
|
|
@@ -44884,11 +45049,13 @@ const TextUI = props => {
|
|
|
44884
45049
|
shrinkWrap,
|
|
44885
45050
|
...rest
|
|
44886
45051
|
} = props;
|
|
45052
|
+
if (capitalize) {
|
|
45053
|
+
children = capitalizeFirstLetter(children);
|
|
45054
|
+
}
|
|
44887
45055
|
const defaultSpace = preventSpaceUnderlines ? FAKE_SPACE : REGULAR_SPACE;
|
|
44888
45056
|
const resolvedSpacing = spacing ?? defaultSpace;
|
|
44889
45057
|
const boxProps = {
|
|
44890
45058
|
"as": "span",
|
|
44891
|
-
"data-capitalize": capitalize ? "" : undefined,
|
|
44892
45059
|
"data-shrinkwrap": shrinkWrap ? "" : undefined,
|
|
44893
45060
|
...rest,
|
|
44894
45061
|
ref,
|
|
@@ -45189,6 +45356,28 @@ const Icon = ({
|
|
|
45189
45356
|
});
|
|
45190
45357
|
};
|
|
45191
45358
|
|
|
45359
|
+
// On the string rather than with ::first-letter, which needs a block container:
|
|
45360
|
+
// an inline text made one is an atomic box, and the ellipsis of the line
|
|
45361
|
+
// holding it (a Picker's value) can then no longer cut it. The string is also
|
|
45362
|
+
// what every copy of the children (sizer, bold background) renders, so they
|
|
45363
|
+
// all read the same. Leading spaces and punctuation are skipped, and a text
|
|
45364
|
+
// starting with a digit is left as it is, as ::first-letter would.
|
|
45365
|
+
const capitalizeFirstLetter = children => {
|
|
45366
|
+
if (typeof children === "string") {
|
|
45367
|
+
return children.replace(FIRST_LOWERCASE_LETTER_REGEXP, (match, lead, letter) => `${lead}${letter.toLocaleUpperCase()}`);
|
|
45368
|
+
}
|
|
45369
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
45370
|
+
const first = children[0];
|
|
45371
|
+
const firstCapitalized = capitalizeFirstLetter(first);
|
|
45372
|
+
if (firstCapitalized === first) {
|
|
45373
|
+
return children;
|
|
45374
|
+
}
|
|
45375
|
+
return [firstCapitalized, ...children.slice(1)];
|
|
45376
|
+
}
|
|
45377
|
+
return children;
|
|
45378
|
+
};
|
|
45379
|
+
const FIRST_LOWERCASE_LETTER_REGEXP = /^([\s\p{P}]*)(\p{Ll})/u;
|
|
45380
|
+
|
|
45192
45381
|
/**
|
|
45193
45382
|
* Where a link leads, asked for when the pointer or the focus arrives on it:
|
|
45194
45383
|
* the moment the user signals a destination, before the press. Only what the
|
|
@@ -60033,7 +60222,6 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
60033
60222
|
panelEl.style.transform = "";
|
|
60034
60223
|
panelEl.style.clipPath = "";
|
|
60035
60224
|
panelEl.style.transitionProperty = "";
|
|
60036
|
-
panelEl.style.userSelect = "";
|
|
60037
60225
|
};
|
|
60038
60226
|
// The resting value is written first and the animation only covers the way
|
|
60039
60227
|
// to it: both end on the same number, so nothing is seen changing when the
|
|
@@ -60077,10 +60265,6 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
60077
60265
|
return false;
|
|
60078
60266
|
}
|
|
60079
60267
|
panelEl.style.transitionProperty = "none";
|
|
60080
|
-
panelEl.style.userSelect = "none";
|
|
60081
|
-
// What the first pixels of the gesture may have started selecting is
|
|
60082
|
-
// not a selection, it is the beginning of this travel.
|
|
60083
|
-
window.getSelection().removeAllRanges();
|
|
60084
60268
|
// The way out is the only way there is anything: pulling the other way
|
|
60085
60269
|
// leans against a wall and comes back.
|
|
60086
60270
|
return {
|
|
@@ -60751,7 +60935,7 @@ const css$E = /* css */`
|
|
|
60751
60935
|
--navi-app-width rather than 3vvw): the gap must read as a small
|
|
60752
60936
|
margin around the dialog, and 3% of a 1500px window is a 45px gap
|
|
60753
60937
|
around a 600px app. Identical to 3vvw until the app declares
|
|
60754
|
-
--navi-app-max-width. */
|
|
60938
|
+
bands (--navi-app-max-width, or --navi-app-inset-* written by it). */
|
|
60755
60939
|
--x-dialog-container-spacing: calc(0.03 * var(--navi-app-width));
|
|
60756
60940
|
|
|
60757
60941
|
/* --navi-app-width, not --navi-vvw: a top-layer dialog is calibrated on
|
|
@@ -61488,8 +61672,8 @@ const css$E = /* css */`
|
|
|
61488
61672
|
* `positionArea`: it both caps the dialog's own size (via
|
|
61489
61673
|
* `--x-dialog-container-spacing`, written from this prop) and offsets a docked
|
|
61490
61674
|
* one from the edge it docks to. Accepts a number of pixels, a viewport
|
|
61491
|
-
* length — "appw"/"apph" being the app's own screen (the visual viewport
|
|
61492
|
-
*
|
|
61675
|
+
* length — "appw"/"apph" being the app's own screen (the visual viewport
|
|
61676
|
+
* minus the app's bands, see docs/safe_area.md) and
|
|
61493
61677
|
* "vvw"/"vvh" the visual viewport itself, which shrinks when the mobile
|
|
61494
61678
|
* keyboard opens — or a container length ("3cqw", the `layer="local"`
|
|
61495
61679
|
* default). Pass 0 for a dialog meant to sit flush (a side panel).
|
|
@@ -64542,6 +64726,8 @@ const css$C = /* css */`@layer navi {
|
|
|
64542
64726
|
* `maxWidth` below.
|
|
64543
64727
|
* @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
|
|
64544
64728
|
* @param {"box"|"scene"} [props.lift] - **Dialog-only**, same guard.
|
|
64729
|
+
* @param {Element|{current: Element}|string} [props.liftAnchor] -
|
|
64730
|
+
* **Dialog-only**, same guard.
|
|
64545
64731
|
* @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
|
|
64546
64732
|
* `Popover` have different own defaults (`"center"` vs. `"bottom"`),
|
|
64547
64733
|
* deliberately not homogenized here (each reads best for its own typical
|
|
@@ -64575,8 +64761,13 @@ const css$C = /* css */`@layer navi {
|
|
|
64575
64761
|
* identically): the wash the backdrop paints over what is behind.
|
|
64576
64762
|
* @param {string} [props.backdropFilter] - Forwarded as-is: what that wash
|
|
64577
64763
|
* does to the picture underneath, `"blur(4px)"` and the like.
|
|
64578
|
-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"
|
|
64579
|
-
* - Forwarded
|
|
64764
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`|"expanding"|`expand-${string}`|"lifting"|{open: boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`, close: "lifting"}} [props.animation]
|
|
64765
|
+
* - Forwarded to whichever renders — see either component's own doc.
|
|
64766
|
+
* `"expanding"`/`"expand-*"` are `Popover`'s own; `"lifting"` and
|
|
64767
|
+
* `{ open, close: "lifting" }` are `Dialog`'s. A popover has no lift, so in
|
|
64768
|
+
* popover mode `"lifting"` plays no movement (what a dialog does with no
|
|
64769
|
+
* anchor to lift out of) and the object form opens with its `open`, which
|
|
64770
|
+
* the close plays backwards like every other kind.
|
|
64580
64771
|
* @param {string} [props.animationDuration] - Forwarded as-is.
|
|
64581
64772
|
* @param {string} [props.maxWidth] - Forwarded as-is to both; also read
|
|
64582
64773
|
* here directly to help decide the automatic `mode` (a fixed length under
|
|
@@ -64642,6 +64833,8 @@ const Popup = props => {
|
|
|
64642
64833
|
// mode the automatic screen-size resolution happens to pick.
|
|
64643
64834
|
pressOutside = "close",
|
|
64644
64835
|
backdrop,
|
|
64836
|
+
// A popover is handed only the values it understands (toPopoverAnimation).
|
|
64837
|
+
animation,
|
|
64645
64838
|
// Popover-only (see this component's own doc) — destructured out so
|
|
64646
64839
|
// they're never part of ...rest, and therefore never forwarded to
|
|
64647
64840
|
// Dialog below, where they'd otherwise leak onto the real <dialog>
|
|
@@ -64656,6 +64849,7 @@ const Popup = props => {
|
|
|
64656
64849
|
dockedOnSmallTouchScreen,
|
|
64657
64850
|
sizeFromAnchor,
|
|
64658
64851
|
lift,
|
|
64852
|
+
liftAnchor,
|
|
64659
64853
|
...rest
|
|
64660
64854
|
} = props;
|
|
64661
64855
|
const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
|
|
@@ -64675,6 +64869,8 @@ const Popup = props => {
|
|
|
64675
64869
|
dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
|
|
64676
64870
|
sizeFromAnchor: sizeFromAnchor,
|
|
64677
64871
|
lift: lift,
|
|
64872
|
+
liftAnchor: liftAnchor,
|
|
64873
|
+
animation: animation,
|
|
64678
64874
|
maxWidth: maxWidth,
|
|
64679
64875
|
pressOutside: pressOutside,
|
|
64680
64876
|
backdrop: backdrop,
|
|
@@ -64688,6 +64884,7 @@ const Popup = props => {
|
|
|
64688
64884
|
}
|
|
64689
64885
|
return jsx(Popover, {
|
|
64690
64886
|
...rest,
|
|
64887
|
+
animation: toPopoverAnimation(animation),
|
|
64691
64888
|
maxWidth: maxWidth,
|
|
64692
64889
|
pressOutside: pressOutside,
|
|
64693
64890
|
backdrop: backdrop,
|
|
@@ -64705,6 +64902,18 @@ const Popup = props => {
|
|
|
64705
64902
|
};
|
|
64706
64903
|
Popup.Close = PopupClose;
|
|
64707
64904
|
|
|
64905
|
+
// What Dialog's own animation values mean to a popover, which has no lift
|
|
64906
|
+
// (see the `animation` doc above).
|
|
64907
|
+
const toPopoverAnimation = animation => {
|
|
64908
|
+
if (animation === "lifting") {
|
|
64909
|
+
return undefined;
|
|
64910
|
+
}
|
|
64911
|
+
if (animation !== null && typeof animation === "object") {
|
|
64912
|
+
return animation.open;
|
|
64913
|
+
}
|
|
64914
|
+
return animation;
|
|
64915
|
+
};
|
|
64916
|
+
|
|
64708
64917
|
installImportMetaCssBuild(import.meta);
|
|
64709
64918
|
const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
64710
64919
|
display: none;
|
|
@@ -66015,9 +66224,14 @@ const PickerNaviMinute = props => {
|
|
|
66015
66224
|
step,
|
|
66016
66225
|
value
|
|
66017
66226
|
} = props;
|
|
66227
|
+
|
|
66228
|
+
// resolveInputProps has already run (PickerFirstResolver), so the type
|
|
66229
|
+
// handed on is one the picker renders as is — the text field a navi numeric
|
|
66230
|
+
// type resolves to, told what its value is.
|
|
66018
66231
|
return jsx(Next, {
|
|
66019
66232
|
...props,
|
|
66020
|
-
type: "
|
|
66233
|
+
type: "text",
|
|
66234
|
+
"navi-input-type": "minute",
|
|
66021
66235
|
children: jsxs(Box, {
|
|
66022
66236
|
flex: "y",
|
|
66023
66237
|
spacing: "s",
|
|
@@ -72613,11 +72827,15 @@ const PickerNaviTime = props => {
|
|
|
72613
72827
|
max = "23:30",
|
|
72614
72828
|
step
|
|
72615
72829
|
} = props;
|
|
72616
|
-
|
|
72830
|
+
// resolveInputProps has already run (PickerFirstResolver) and left navi_time
|
|
72831
|
+
// as written: what is handed on is a time input, whose step is in seconds.
|
|
72832
|
+
const hostStep = timeStringToSeconds(step);
|
|
72833
|
+
const stepSeconds = hostStep ?? 1800;
|
|
72617
72834
|
const slots = useMemo(() => generateTimeSlots(min, max, stepSeconds), [min, max, stepSeconds]);
|
|
72618
72835
|
return jsx(Next, {
|
|
72619
72836
|
...props,
|
|
72620
72837
|
type: "time",
|
|
72838
|
+
step: hostStep,
|
|
72621
72839
|
children: jsx(List, {
|
|
72622
72840
|
selectable: true,
|
|
72623
72841
|
command: "--navi-send",
|
|
@@ -75029,6 +75247,10 @@ const PickerFirstResolver = props => {
|
|
|
75029
75247
|
...props
|
|
75030
75248
|
});
|
|
75031
75249
|
};
|
|
75250
|
+
|
|
75251
|
+
// PickerFirstResolver comes before the presets so a preset reads the
|
|
75252
|
+
// min/max/step a bound signal fills in (resolveInputProps); a preset type
|
|
75253
|
+
// therefore has no row in NAVI_TYPE_DEFAULTS (resolve_input_props.js).
|
|
75032
75254
|
const PickerResolved = /*#__PURE__*/createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerConfirmResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
75033
75255
|
const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
75034
75256
|
Date: PickerDateUI,
|
|
@@ -86439,6 +86661,8 @@ const css$3 = /* css */`@layer navi {
|
|
|
86439
86661
|
}
|
|
86440
86662
|
|
|
86441
86663
|
.navi_card_layout {
|
|
86664
|
+
box-sizing: border-box;
|
|
86665
|
+
min-height: 100%;
|
|
86442
86666
|
padding-top: var(--layout-margin-top, var(--layout-margin-y, var(--layout-margin)));
|
|
86443
86667
|
padding-right: var(--layout-margin-right, var(--layout-margin-x, var(--layout-margin)));
|
|
86444
86668
|
padding-bottom: var(--layout-margin-bottom, var(--layout-margin-y, var(--layout-margin)));
|
|
@@ -86480,6 +86704,11 @@ const CardLayoutStyleCSSVars = {
|
|
|
86480
86704
|
minHeight: "--layout-min-height"
|
|
86481
86705
|
};
|
|
86482
86706
|
/**
|
|
86707
|
+
* The card is centered in the area both ways and is as wide as its content,
|
|
86708
|
+
* `minWidth` at least. Down the area, the layout takes at least the height of
|
|
86709
|
+
* what holds it, so how tall that area is is up to the page around it, as for
|
|
86710
|
+
* `ViewportLayout`.
|
|
86711
|
+
*
|
|
86483
86712
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
86484
86713
|
* alignX?: string,
|
|
86485
86714
|
* alignY?: string,
|
|
@@ -86509,6 +86738,11 @@ const CardLayout = ({
|
|
|
86509
86738
|
}) => {
|
|
86510
86739
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
86511
86740
|
return jsx(Box, {
|
|
86741
|
+
flex: true
|
|
86742
|
+
// The card's place in the area; alignX/alignY below are its content's.
|
|
86743
|
+
,
|
|
86744
|
+
alignX: "center",
|
|
86745
|
+
alignY: "center",
|
|
86512
86746
|
baseClassName: "navi_card_layout",
|
|
86513
86747
|
styleCSSVars: CardLayoutStyleCSSVars,
|
|
86514
86748
|
visualSelector: ".navi_card",
|