@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/dev/jsenv_navi.js
CHANGED
|
@@ -554,7 +554,10 @@ effect(() => {
|
|
|
554
554
|
* The active language is read from `languagesSignal` (see lang_signal.js —
|
|
555
555
|
* combines the browser's own `navigator.languages`, an optional
|
|
556
556
|
* `setPreferredLanguage()` user override, and an optional
|
|
557
|
-
* `setSupportedLanguages()` app-wide allow-list), live on every lookup.
|
|
557
|
+
* `setSupportedLanguages()` app-wide allow-list), live on every lookup. Each
|
|
558
|
+
* key is looked up in those languages in order, then in English, which
|
|
559
|
+
* translates every key: a language shipped for part of the keys only (German
|
|
560
|
+
* has the `time.*` words) never shows a raw key.
|
|
558
561
|
*
|
|
559
562
|
* Built-in key namespaces, all overridable — the registrations below are the
|
|
560
563
|
* exhaustive list, read them to find the exact key to override:
|
|
@@ -1474,19 +1477,25 @@ const isNetworkPolicyError = (error) => {
|
|
|
1474
1477
|
* is already saying it in words the person understands.
|
|
1475
1478
|
*
|
|
1476
1479
|
* navi's own report leaves it alone (action_error_report.js). What is left is
|
|
1477
|
-
* the
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1481
|
-
* asked. Cancelling the event is what says it is handled: the browser drops
|
|
1480
|
+
* the error reaching `window` on its own — a run called by hand whose failure
|
|
1481
|
+
* nobody catches, a render throw no boundary took — where it lands as an
|
|
1482
|
+
* uncaught error in the console, about a request the app itself declared would
|
|
1483
|
+
* not leave. Cancelling the event is what says it is handled: the browser drops
|
|
1482
1484
|
* the console line, and the jsenv supervisor skips prevented events too.
|
|
1483
1485
|
*
|
|
1484
1486
|
* Both shapes a failure travels in are covered, since which one it is depends
|
|
1485
1487
|
* on whether the resource callback happened to be async — the throw reaching
|
|
1486
1488
|
* `window`, and the rejection nobody caught.
|
|
1487
1489
|
*
|
|
1488
|
-
*
|
|
1489
|
-
*
|
|
1490
|
+
* A boundary displaying it raises no event at all. In dev, `preact/debug` hands
|
|
1491
|
+
* every error a boundary caught to `console.error`, after the boundary took it
|
|
1492
|
+
* and before it rendered anything; it reads no option and no flag, so that line
|
|
1493
|
+
* stays. Muting it from here is not done: wrapping `console.error` rewrites a
|
|
1494
|
+
* global, and wrapping `options._catchError` reaches into preact's private
|
|
1495
|
+
* hooks, both to hide one class of error.
|
|
1496
|
+
*
|
|
1497
|
+
* Scoped to the policy's own error and to nothing else — every other error
|
|
1498
|
+
* thrown at window, and every other rejection let go, stays exactly as loud as
|
|
1490
1499
|
* it is.
|
|
1491
1500
|
*/
|
|
1492
1501
|
let networkPolicyErrorSilenced = false;
|
|
@@ -1652,8 +1661,8 @@ const markErrorAsStoppingRender = (error) => {
|
|
|
1652
1661
|
* An error the network policy produced is never reported: the app declared the
|
|
1653
1662
|
* state that produced it, the request never left, and there is nothing for a
|
|
1654
1663
|
* developer to fix. It is data a screen shows, and it stays data whether or not
|
|
1655
|
-
* one does (see network_policy.js, which also
|
|
1656
|
-
*
|
|
1664
|
+
* one does (see network_policy.js, which also cancels it when it reaches
|
|
1665
|
+
* window).
|
|
1657
1666
|
*/
|
|
1658
1667
|
const errorIsAccountedFor = (error) => {
|
|
1659
1668
|
return (
|
|
@@ -4946,14 +4955,11 @@ const CONTROL_TYPE_BY_SIGNAL_TYPE = {
|
|
|
4946
4955
|
// become something plainer — it is what says the value is a number (see
|
|
4947
4956
|
// isNumberInput), and what lets constraint messages use domain-specific
|
|
4948
4957
|
// wording instead of the generic "Ce nombre doit être...".
|
|
4958
|
+
// A type a picker preset draws (navi_time, navi_minute: see picker_preset.jsx)
|
|
4959
|
+
// has no row: resolveInputProps runs before the preset resolver, and a row
|
|
4960
|
+
// would turn the type into its host before the preset resolver reads it. The
|
|
4961
|
+
// preset hands the host type on itself.
|
|
4949
4962
|
const NAVI_TYPE_DEFAULTS = {
|
|
4950
|
-
navi_time: {
|
|
4951
|
-
"type": "time",
|
|
4952
|
-
"navi-input-type": "time",
|
|
4953
|
-
"min": 0,
|
|
4954
|
-
"max": 24 * 3600 - 1,
|
|
4955
|
-
"step": 1,
|
|
4956
|
-
},
|
|
4957
4963
|
navi_percentage: {
|
|
4958
4964
|
"type": "navi_number",
|
|
4959
4965
|
"navi-input-type": "percentage",
|
|
@@ -5015,7 +5021,6 @@ const isNumberInput = (type, naviInputType) => {
|
|
|
5015
5021
|
* Supported navi types and their targets:
|
|
5016
5022
|
* - `navi_percentage` → `navi_number` (0–100, step 1)
|
|
5017
5023
|
* - `navi_number` → `text` (inputMode="numeric", no spin buttons implied)
|
|
5018
|
-
* - `navi_time` → `time` (step in seconds)
|
|
5019
5024
|
*
|
|
5020
5025
|
* Standard HTML input types with formatters:
|
|
5021
5026
|
* - `date`, `month`, `week`, `time`, `datetime-local`, `datetime`:
|
|
@@ -12640,8 +12645,9 @@ defineInteractionDetector({
|
|
|
12640
12645
|
name: "surface",
|
|
12641
12646
|
claims: (type) =>
|
|
12642
12647
|
type === PAN || type === ZOOM || type === GRAB || type === RELEASE,
|
|
12643
|
-
// A press on the surface may be a tap, a hold or the
|
|
12644
|
-
// nothing may read it as the first until the pointer has said
|
|
12648
|
+
// A press on the surface may be a tap, a hold or the first finger of a pan or
|
|
12649
|
+
// a pinch, and nothing may read it as the first until the pointer has said
|
|
12650
|
+
// which.
|
|
12645
12651
|
disputesPress: true,
|
|
12646
12652
|
setup: (element, trigger, { types, readConfig }) => {
|
|
12647
12653
|
const canPan = types.includes(PAN);
|
|
@@ -15763,21 +15769,33 @@ const getActionResultProperties = (action) => {
|
|
|
15763
15769
|
return actionResultPropertiesMap.get(action);
|
|
15764
15770
|
};
|
|
15765
15771
|
|
|
15766
|
-
// PUT/PATCH results update the UI through the store, and DELETE resets the GET
|
|
15767
|
-
// instead of rerunning it, so a GET rerun would only ever cost a request.
|
|
15768
|
-
// A POST is the one case the client cannot decide alone: whether a new item
|
|
15769
|
-
// belongs to a list depends on filters/pagination the backend owns.
|
|
15770
15772
|
// Rationale in full, plus when to override: docs/list_refresh.md
|
|
15771
15773
|
const defaultRerunOn = {
|
|
15774
|
+
// PUT/PATCH results update the UI through the store, and DELETE resets the
|
|
15775
|
+
// GET rather than rerunning it, so a GET rerun would only ever cost a request.
|
|
15772
15776
|
GET: false,
|
|
15777
|
+
// A POST is the one case the client cannot decide alone: whether a new item
|
|
15778
|
+
// belongs to a list depends on filters/pagination the backend owns.
|
|
15773
15779
|
GET_MANY: ["POST"],
|
|
15780
|
+
// DELETE is in there, unlike for GET_MANY: an action holds ids and the store
|
|
15781
|
+
// drops the deleted one out of every list holding it, while a list reading
|
|
15782
|
+
// by slices holds places — the row that left takes the ones after it one
|
|
15783
|
+
// rank up, and only the collection knows who fills the last one.
|
|
15784
|
+
GET_RANGE: ["POST", "DELETE"],
|
|
15785
|
+
};
|
|
15786
|
+
// A key a `rerunOn` leaves out keeps the value it would have had without it:
|
|
15787
|
+
// the default for a resource, the value of the resource it is declared on for
|
|
15788
|
+
// a withParams() scope or a relation.
|
|
15789
|
+
const resolveRerunOn = (rerunOn, inheritedRerunOn = defaultRerunOn) => {
|
|
15790
|
+
if (!rerunOn) {
|
|
15791
|
+
return inheritedRerunOn;
|
|
15792
|
+
}
|
|
15793
|
+
return {
|
|
15794
|
+
GET: rerunOn.GET ?? inheritedRerunOn.GET,
|
|
15795
|
+
GET_MANY: rerunOn.GET_MANY ?? inheritedRerunOn.GET_MANY,
|
|
15796
|
+
GET_RANGE: rerunOn.GET_RANGE ?? inheritedRerunOn.GET_RANGE,
|
|
15797
|
+
};
|
|
15774
15798
|
};
|
|
15775
|
-
// What makes a range reader stale (rerunOn.GET_RANGE overrides it). DELETE is
|
|
15776
|
-
// in there, unlike for GET_MANY: an action holds ids and the store drops the
|
|
15777
|
-
// deleted one out of every list holding it, while a list reading by slices
|
|
15778
|
-
// holds places — the row that left takes the ones after it one rank up, and
|
|
15779
|
-
// only the collection knows who fills the last one.
|
|
15780
|
-
const defaultInvalidateRangeOn = ["POST", "DELETE"];
|
|
15781
15799
|
|
|
15782
15800
|
// This handles ALL resource lifecycle logic (rerun/reset) across all resources
|
|
15783
15801
|
const createResourceLifecycleManager = () => {
|
|
@@ -15787,7 +15805,7 @@ const createResourceLifecycleManager = () => {
|
|
|
15787
15805
|
|
|
15788
15806
|
const registerResource = (resourceScope, config) => {
|
|
15789
15807
|
const {
|
|
15790
|
-
rerunOn
|
|
15808
|
+
rerunOn,
|
|
15791
15809
|
paramScope = null,
|
|
15792
15810
|
dependencies = [],
|
|
15793
15811
|
uniqueKeys = [],
|
|
@@ -16070,8 +16088,7 @@ const createResourceLifecycleManager = () => {
|
|
|
16070
16088
|
if (!isSameResource && !isDependent) {
|
|
16071
16089
|
continue;
|
|
16072
16090
|
}
|
|
16073
|
-
|
|
16074
|
-
if (!shouldRerunAfter(invalidateOn, triggerVerb)) {
|
|
16091
|
+
if (!shouldRerunAfter(config.rerunOn.GET_RANGE, triggerVerb)) {
|
|
16075
16092
|
continue;
|
|
16076
16093
|
}
|
|
16077
16094
|
for (const rangeReader of config.rangeReaderSet) {
|
|
@@ -16572,7 +16589,7 @@ const resource = (
|
|
|
16572
16589
|
addItemSetup,
|
|
16573
16590
|
createRestAction: createRestActionForRoot,
|
|
16574
16591
|
paramScope: getParamScope(undefined),
|
|
16575
|
-
rerunOn,
|
|
16592
|
+
rerunOn: resolveRerunOn(rerunOn),
|
|
16576
16593
|
dependencies,
|
|
16577
16594
|
});
|
|
16578
16595
|
};
|
|
@@ -16590,6 +16607,15 @@ const createResource = (
|
|
|
16590
16607
|
paramScope,
|
|
16591
16608
|
rerunOn,
|
|
16592
16609
|
dependencies,
|
|
16610
|
+
// Set on a scoped child: the resource owning each of its items, which
|
|
16611
|
+
// live in one store per owner (`store` is the owner's).
|
|
16612
|
+
ownerName,
|
|
16613
|
+
// Set on a scopedMany child: its per-owner store looked up by owner key,
|
|
16614
|
+
// and the owner whose GET embeds the collection. Options rather than
|
|
16615
|
+
// properties added afterwards, so every facade of the relation — the one
|
|
16616
|
+
// scopedMany returns and each withParams() of it — carries both.
|
|
16617
|
+
getChildStore,
|
|
16618
|
+
ownerDependency,
|
|
16593
16619
|
},
|
|
16594
16620
|
) => {
|
|
16595
16621
|
const params = paramScope.params;
|
|
@@ -16615,6 +16641,8 @@ const createResource = (
|
|
|
16615
16641
|
// private but exposed for convenience
|
|
16616
16642
|
store,
|
|
16617
16643
|
addItemSetup,
|
|
16644
|
+
ownerName,
|
|
16645
|
+
getChildStore,
|
|
16618
16646
|
};
|
|
16619
16647
|
|
|
16620
16648
|
resourceLifecycleManager.registerResource(stateFacade, {
|
|
@@ -16623,6 +16651,17 @@ const createResource = (
|
|
|
16623
16651
|
dependencies,
|
|
16624
16652
|
uniqueKeys,
|
|
16625
16653
|
});
|
|
16654
|
+
if (ownerDependency) {
|
|
16655
|
+
// A child collection mutated (POST etc.) makes the owner GET re-fetch:
|
|
16656
|
+
// the owner embeds the child array and only the backend knows the new
|
|
16657
|
+
// ordering. Registered per facade, since the rerun is looked up by the
|
|
16658
|
+
// facade whose action completed.
|
|
16659
|
+
resourceLifecycleManager.addDependency(
|
|
16660
|
+
stateFacade,
|
|
16661
|
+
ownerDependency.owner,
|
|
16662
|
+
ownerDependency.propertyName,
|
|
16663
|
+
);
|
|
16664
|
+
}
|
|
16626
16665
|
const onActionComplete = (actionCompleted) => {
|
|
16627
16666
|
resourceLifecycleManager.onActionComplete(actionCompleted, {
|
|
16628
16667
|
resourceScope: stateFacade,
|
|
@@ -16635,8 +16674,12 @@ const createResource = (
|
|
|
16635
16674
|
* Actions from parameterized resources only trigger rerun/reset for other actions with
|
|
16636
16675
|
* identical parameters, preventing cross-contamination between different parameter sets.
|
|
16637
16676
|
*
|
|
16677
|
+
* The actions are the resource's own kind: on a relation they keep its callback return
|
|
16678
|
+
* contract and write where it writes (`TABLE_COLUMNS.withParams(…).GET_MANY` returns
|
|
16679
|
+
* `[ownerId, items]` and replaces that owner's collection).
|
|
16680
|
+
*
|
|
16638
16681
|
* @param {Object} params - Parameters to bind to all actions of this resource (required)
|
|
16639
|
-
* @param {{ rerunOn?: Object, dependencies?: Object[] }} [options] - reruns of that scope; left out
|
|
16682
|
+
* @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
|
|
16640
16683
|
* @returns {Object} A new resource instance with parameter-bound actions and isolated lifecycle
|
|
16641
16684
|
* @see docs/resource.md — what a scope isolates, and `dependencies`
|
|
16642
16685
|
*
|
|
@@ -16660,7 +16703,6 @@ const createResource = (
|
|
|
16660
16703
|
paramsToInject,
|
|
16661
16704
|
{ dependencies: withParamsDeps, rerunOn: withParamsRerunOn } = {},
|
|
16662
16705
|
) => {
|
|
16663
|
-
const declarationSite = getDeclarationSite();
|
|
16664
16706
|
if (!paramsToInject || Object.keys(paramsToInject).length === 0) {
|
|
16665
16707
|
throw new Error(`resource(${name}).withParams() requires parameters`);
|
|
16666
16708
|
}
|
|
@@ -16668,11 +16710,6 @@ const createResource = (
|
|
|
16668
16710
|
? { ...params, ...paramsToInject }
|
|
16669
16711
|
: paramsToInject;
|
|
16670
16712
|
const resolvedParamScope = getParamScope(resolvedParams);
|
|
16671
|
-
const createRestActionWithParams = createRestActionFactoryForRoot(name, {
|
|
16672
|
-
idKey,
|
|
16673
|
-
store,
|
|
16674
|
-
declarationSite,
|
|
16675
|
-
});
|
|
16676
16713
|
return createResource(name, {
|
|
16677
16714
|
idKey,
|
|
16678
16715
|
uniqueKeys,
|
|
@@ -16680,10 +16717,16 @@ const createResource = (
|
|
|
16680
16717
|
restCallbacks,
|
|
16681
16718
|
store,
|
|
16682
16719
|
addItemSetup,
|
|
16683
|
-
|
|
16720
|
+
// The actions of this resource's kind: a relation applies its results to
|
|
16721
|
+
// its relation (a scoped child to the per-owner stores), never as root
|
|
16722
|
+
// items of `store`.
|
|
16723
|
+
createRestAction,
|
|
16684
16724
|
paramScope: resolvedParamScope,
|
|
16685
|
-
rerunOn: withParamsRerunOn
|
|
16725
|
+
rerunOn: resolveRerunOn(withParamsRerunOn, rerunOn),
|
|
16686
16726
|
dependencies: withParamsDeps ?? dependencies,
|
|
16727
|
+
ownerName,
|
|
16728
|
+
getChildStore,
|
|
16729
|
+
ownerDependency,
|
|
16687
16730
|
});
|
|
16688
16731
|
};
|
|
16689
16732
|
stateFacade.withParams = withParams;
|
|
@@ -16700,14 +16743,15 @@ const createResource = (
|
|
|
16700
16743
|
* Callback return contracts:
|
|
16701
16744
|
* - GET / PUT → the parent object with the relationship nested inside:
|
|
16702
16745
|
* `async ({ id }) => ({ id, session: { id: 10, token: "abc" } })`; `null` for no relationship
|
|
16703
|
-
* - DELETE → the parent id
|
|
16746
|
+
* - DELETE → the parent id, or `{ id }`; the property is set to `null`
|
|
16704
16747
|
*
|
|
16705
16748
|
* The backend may also embed the child inline in a parent GET/POST response — the
|
|
16706
16749
|
* setter on the property upserts the nested object into the child store.
|
|
16707
16750
|
*
|
|
16708
|
-
* Returns the relationship resource
|
|
16709
|
-
*
|
|
16710
|
-
*
|
|
16751
|
+
* Returns the relationship resource, itself chainable. Its items are the child
|
|
16752
|
+
* resource's own, shared by every parent: `USER_SESSION.one("device", DEVICE)`
|
|
16753
|
+
* adds a reactive `.device` property to every session, as
|
|
16754
|
+
* `SESSION.one("device", DEVICE)` would.
|
|
16711
16755
|
*
|
|
16712
16756
|
* @param {string} propertyName - property holding the child on each parent item
|
|
16713
16757
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
@@ -16727,6 +16771,13 @@ const createResource = (
|
|
|
16727
16771
|
} = {},
|
|
16728
16772
|
) => {
|
|
16729
16773
|
const declarationSite = getDeclarationSite();
|
|
16774
|
+
if (ownerName) {
|
|
16775
|
+
refuseCallbacksNamingScopedItem(
|
|
16776
|
+
`${name}.one("${propertyName}")`,
|
|
16777
|
+
{ GET, PUT, DELETE },
|
|
16778
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
16779
|
+
);
|
|
16780
|
+
}
|
|
16730
16781
|
const childName = `${name}.${propertyName}`;
|
|
16731
16782
|
const childIdKey = childResource.idKey;
|
|
16732
16783
|
const childStore = childResource.store;
|
|
@@ -16787,14 +16838,18 @@ const createResource = (
|
|
|
16787
16838
|
);
|
|
16788
16839
|
});
|
|
16789
16840
|
|
|
16790
|
-
const createRestActionForOne = (
|
|
16841
|
+
const createRestActionForOne = (
|
|
16842
|
+
verb,
|
|
16843
|
+
callback,
|
|
16844
|
+
{ onActionComplete, paramScope },
|
|
16845
|
+
) => {
|
|
16791
16846
|
const applyResultToValue =
|
|
16792
16847
|
verb === "DELETE"
|
|
16793
|
-
? (
|
|
16794
|
-
const item = store.select(
|
|
16848
|
+
? (itemIdOrItemProps) => {
|
|
16849
|
+
const item = store.select(itemIdOrItemProps);
|
|
16795
16850
|
const childItemId = item[propertyName][childIdKey];
|
|
16796
16851
|
store.upsert({
|
|
16797
|
-
[idKey]:
|
|
16852
|
+
[idKey]: item[idKey],
|
|
16798
16853
|
[propertyName]: null,
|
|
16799
16854
|
});
|
|
16800
16855
|
return childItemId;
|
|
@@ -16822,7 +16877,7 @@ const createResource = (
|
|
|
16822
16877
|
if (!isProps(result) && !primitiveCanBeId(result)) {
|
|
16823
16878
|
throwInvalidResult(
|
|
16824
16879
|
action.name,
|
|
16825
|
-
`
|
|
16880
|
+
`the "${name}" id, or { ${idKey} } (the item whose "${propertyName}" becomes null)`,
|
|
16826
16881
|
result,
|
|
16827
16882
|
);
|
|
16828
16883
|
}
|
|
@@ -16847,11 +16902,11 @@ const createResource = (
|
|
|
16847
16902
|
PUT,
|
|
16848
16903
|
DELETE,
|
|
16849
16904
|
},
|
|
16850
|
-
store,
|
|
16851
|
-
addItemSetup,
|
|
16905
|
+
store: childStore,
|
|
16906
|
+
addItemSetup: childResource.addItemSetup,
|
|
16852
16907
|
createRestAction: createRestActionForOne,
|
|
16853
16908
|
paramScope,
|
|
16854
|
-
rerunOn: oneRerunOn
|
|
16909
|
+
rerunOn: resolveRerunOn(oneRerunOn, rerunOn),
|
|
16855
16910
|
dependencies: oneDependencies ?? dependencies,
|
|
16856
16911
|
});
|
|
16857
16912
|
};
|
|
@@ -16874,6 +16929,9 @@ const createResource = (
|
|
|
16874
16929
|
* - DELETE → `[parentId, childId]`
|
|
16875
16930
|
* - DELETE_MANY → `[parentId, [childId, childId, …]]`
|
|
16876
16931
|
*
|
|
16932
|
+
* Returns the relationship resource, itself chainable; its items are the child
|
|
16933
|
+
* resource's own, as for `.one()`.
|
|
16934
|
+
*
|
|
16877
16935
|
* @param {string} propertyName - property holding the child array on each parent item
|
|
16878
16936
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
16879
16937
|
* @param {Object} [restCallbacks] - `{ rerunOn, dependencies, GET, GET_MANY, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
@@ -16899,6 +16957,13 @@ const createResource = (
|
|
|
16899
16957
|
} = {},
|
|
16900
16958
|
) => {
|
|
16901
16959
|
const declarationSite = getDeclarationSite();
|
|
16960
|
+
if (ownerName) {
|
|
16961
|
+
refuseCallbacksNamingScopedItem(
|
|
16962
|
+
`${name}.many("${propertyName}")`,
|
|
16963
|
+
{ GET_MANY, DELETE, DELETE_MANY },
|
|
16964
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
16965
|
+
);
|
|
16966
|
+
}
|
|
16902
16967
|
const childStore = childResource.store;
|
|
16903
16968
|
const childIdKey = childResource.idKey;
|
|
16904
16969
|
const childName = `${name}.${propertyName}`;
|
|
@@ -16930,21 +16995,23 @@ const createResource = (
|
|
|
16930
16995
|
const createRestActionForMany = (
|
|
16931
16996
|
verb,
|
|
16932
16997
|
callback,
|
|
16933
|
-
{ isMany, onActionComplete },
|
|
16998
|
+
{ isMany, onActionComplete, paramScope },
|
|
16934
16999
|
) => {
|
|
16935
17000
|
if (!isMany) {
|
|
16936
17001
|
return createRestActionAffectingOneItem(verb, callback, {
|
|
16937
17002
|
onActionComplete,
|
|
17003
|
+
paramScope,
|
|
16938
17004
|
});
|
|
16939
17005
|
}
|
|
16940
17006
|
return createRestActionAffectingManyItems(verb, callback, {
|
|
16941
17007
|
onActionComplete,
|
|
17008
|
+
paramScope,
|
|
16942
17009
|
});
|
|
16943
17010
|
};
|
|
16944
17011
|
const createRestActionAffectingOneItem = (
|
|
16945
17012
|
verb,
|
|
16946
17013
|
callback,
|
|
16947
|
-
{ onActionComplete },
|
|
17014
|
+
{ onActionComplete, paramScope },
|
|
16948
17015
|
) => {
|
|
16949
17016
|
const applyResultToValue =
|
|
16950
17017
|
verb === "DELETE"
|
|
@@ -17007,7 +17074,7 @@ const createResource = (
|
|
|
17007
17074
|
const createRestActionAffectingManyItems = (
|
|
17008
17075
|
verb,
|
|
17009
17076
|
callback,
|
|
17010
|
-
{ onActionComplete },
|
|
17077
|
+
{ onActionComplete, paramScope },
|
|
17011
17078
|
) => {
|
|
17012
17079
|
const applyResultToValue =
|
|
17013
17080
|
verb === "GET"
|
|
@@ -17109,11 +17176,11 @@ const createResource = (
|
|
|
17109
17176
|
DELETE,
|
|
17110
17177
|
DELETE_MANY,
|
|
17111
17178
|
},
|
|
17112
|
-
store,
|
|
17113
|
-
addItemSetup,
|
|
17179
|
+
store: childStore,
|
|
17180
|
+
addItemSetup: childResource.addItemSetup,
|
|
17114
17181
|
createRestAction: createRestActionForMany,
|
|
17115
17182
|
paramScope,
|
|
17116
|
-
rerunOn: manyRerunOn
|
|
17183
|
+
rerunOn: resolveRerunOn(manyRerunOn, rerunOn),
|
|
17117
17184
|
dependencies: manyDependencies ?? dependencies,
|
|
17118
17185
|
});
|
|
17119
17186
|
};
|
|
@@ -17135,7 +17202,12 @@ const createResource = (
|
|
|
17135
17202
|
* Mutations apply directly to the owner's signal, so the parent GET is never rerun.
|
|
17136
17203
|
*
|
|
17137
17204
|
* Returns the child relationship resource, itself chainable:
|
|
17138
|
-
* `USER_PROFILE.one("theme", THEME)` adds a reactive `.theme` property on each profile
|
|
17205
|
+
* `USER_PROFILE.one("theme", THEME)` adds a reactive `.theme` property on each profile,
|
|
17206
|
+
* fed by what this resource's callbacks embed (`[id, { bio, theme: { id: 3 } }]`). A
|
|
17207
|
+
* profile exists only inside its user, so the chained relation declares none of the
|
|
17208
|
+
* callbacks whose result names a profile (`.one()` GET/PUT/DELETE, `.many()`
|
|
17209
|
+
* GET_MANY/DELETE/DELETE_MANY, any `.scopedOne()`/`.scopedMany()` callback): such a
|
|
17210
|
+
* result cannot say which user holds it, and the declaration throws.
|
|
17139
17211
|
*
|
|
17140
17212
|
* @param {string} propertyName - property holding the sub-object on each owner item
|
|
17141
17213
|
* @param {Object} [restCallbacks] - `{ idKey, rerunOn, dependencies, GET, POST, PUT, PATCH, DELETE }`
|
|
@@ -17155,6 +17227,14 @@ const createResource = (
|
|
|
17155
17227
|
DELETE,
|
|
17156
17228
|
} = {},
|
|
17157
17229
|
) => {
|
|
17230
|
+
const declarationSite = getDeclarationSite();
|
|
17231
|
+
if (ownerName) {
|
|
17232
|
+
refuseCallbacksNamingScopedItem(
|
|
17233
|
+
`${name}.scopedOne("${propertyName}")`,
|
|
17234
|
+
{ GET, POST, PUT, PATCH, DELETE },
|
|
17235
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
17236
|
+
);
|
|
17237
|
+
}
|
|
17158
17238
|
const childName = `${name}.${propertyName}`;
|
|
17159
17239
|
|
|
17160
17240
|
// Callbacks added by chained .one()/.many() on the child resource,
|
|
@@ -17184,7 +17264,12 @@ const createResource = (
|
|
|
17184
17264
|
childSignal.value = childItem; // first activation: null → childItem
|
|
17185
17265
|
}
|
|
17186
17266
|
};
|
|
17187
|
-
|
|
17267
|
+
if (!ownerName) {
|
|
17268
|
+
// Declared on a scoped child, an owner id is only unique inside its
|
|
17269
|
+
// own owner and no callback addresses it (see
|
|
17270
|
+
// refuseCallbacksNamingScopedItem): nothing is looked up by it.
|
|
17271
|
+
applyPropsMap.set(ownerId, applyProps);
|
|
17272
|
+
}
|
|
17188
17273
|
|
|
17189
17274
|
applyProps(ownerItem[propertyName]);
|
|
17190
17275
|
|
|
@@ -17196,7 +17281,7 @@ const createResource = (
|
|
|
17196
17281
|
const createRestActionForScopedOne = (
|
|
17197
17282
|
verb,
|
|
17198
17283
|
callback,
|
|
17199
|
-
{ onActionComplete },
|
|
17284
|
+
{ onActionComplete, paramScope },
|
|
17200
17285
|
) => {
|
|
17201
17286
|
const childActionName = `${childName}.${verb}`;
|
|
17202
17287
|
return createAction(callback, {
|
|
@@ -17242,8 +17327,9 @@ const createResource = (
|
|
|
17242
17327
|
addItemSetup: childAddItemSetup,
|
|
17243
17328
|
createRestAction: createRestActionForScopedOne,
|
|
17244
17329
|
paramScope,
|
|
17245
|
-
rerunOn: scopedOneRerunOn
|
|
17330
|
+
rerunOn: resolveRerunOn(scopedOneRerunOn, rerunOn),
|
|
17246
17331
|
dependencies: scopedOneDependencies ?? dependencies,
|
|
17332
|
+
ownerName: name,
|
|
17247
17333
|
});
|
|
17248
17334
|
};
|
|
17249
17335
|
|
|
@@ -17271,7 +17357,14 @@ const createResource = (
|
|
|
17271
17357
|
* `propertyName`), and the child's own GET_MANY reruns per its `rerunOn`.
|
|
17272
17358
|
*
|
|
17273
17359
|
* Returns the child relationship resource, itself chainable:
|
|
17274
|
-
* `TABLE_COLUMNS.one("dataType", DATA_TYPE)` adds a reactive `.dataType` property on each column
|
|
17360
|
+
* `TABLE_COLUMNS.one("dataType", DATA_TYPE)` adds a reactive `.dataType` property on each column,
|
|
17361
|
+
* fed by what this resource's callbacks embed (`[id, [{ name, dataType: { id: 2 } }]]`). A
|
|
17362
|
+
* column exists only inside its table — two tables can each have an `email` — so the chained
|
|
17363
|
+
* relation declares none of the callbacks whose result names a column (`.one()`
|
|
17364
|
+
* GET/PUT/DELETE, `.many()` GET_MANY/DELETE/DELETE_MANY, any `.scopedOne()`/`.scopedMany()`
|
|
17365
|
+
* callback): such a result cannot say which table holds it, and the declaration throws.
|
|
17366
|
+
* A `.scopedMany()` chained on it keeps one collection per column: `users.email` and
|
|
17367
|
+
* `admins.email` never share one.
|
|
17275
17368
|
*
|
|
17276
17369
|
* @param {string} propertyName - property holding the collection on each owner item
|
|
17277
17370
|
* @param {Object} [restCallbacks] - `{ idKey, rerunOn, dependencies, GET, GET_MANY, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
@@ -17296,6 +17389,25 @@ const createResource = (
|
|
|
17296
17389
|
DELETE_MANY,
|
|
17297
17390
|
} = {},
|
|
17298
17391
|
) => {
|
|
17392
|
+
const declarationSite = getDeclarationSite();
|
|
17393
|
+
if (ownerName) {
|
|
17394
|
+
refuseCallbacksNamingScopedItem(
|
|
17395
|
+
`${name}.scopedMany("${propertyName}")`,
|
|
17396
|
+
{
|
|
17397
|
+
GET,
|
|
17398
|
+
GET_MANY,
|
|
17399
|
+
POST,
|
|
17400
|
+
POST_MANY,
|
|
17401
|
+
PUT,
|
|
17402
|
+
PUT_MANY,
|
|
17403
|
+
PATCH,
|
|
17404
|
+
PATCH_MANY,
|
|
17405
|
+
DELETE,
|
|
17406
|
+
DELETE_MANY,
|
|
17407
|
+
},
|
|
17408
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
17409
|
+
);
|
|
17410
|
+
}
|
|
17299
17411
|
const childName = `${name}.${propertyName}`;
|
|
17300
17412
|
|
|
17301
17413
|
// Callbacks added by chained .one()/.many() on the child resource,
|
|
@@ -17317,37 +17429,42 @@ const createResource = (
|
|
|
17317
17429
|
return childItem;
|
|
17318
17430
|
},
|
|
17319
17431
|
});
|
|
17320
|
-
|
|
17321
|
-
scopeMap.set(ownerKey, scope);
|
|
17322
|
-
return scope;
|
|
17432
|
+
return { childStore, idArraySignal: signal([]) };
|
|
17323
17433
|
};
|
|
17324
17434
|
addItemSetup((item) => {
|
|
17325
17435
|
const ownerId = item[idKey];
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17436
|
+
let scope;
|
|
17437
|
+
if (ownerName) {
|
|
17438
|
+
// The owner is itself a scoped child: its id is only unique inside its
|
|
17439
|
+
// own owner, so the scope is the item's alone, never looked up by id
|
|
17440
|
+
// (no callback addresses it, see refuseCallbacksNamingScopedItem).
|
|
17441
|
+
scope = createScope(ownerId);
|
|
17442
|
+
} else {
|
|
17443
|
+
// Reuse an existing scope if one was already created under a uniqueKey
|
|
17444
|
+
// value (e.g. rows were fetched by tablename before the full table was loaded).
|
|
17445
|
+
scope = scopeMap.get(ownerId);
|
|
17446
|
+
if (!scope) {
|
|
17447
|
+
for (const uniqueKey of uniqueKeys) {
|
|
17448
|
+
const uniqueKeyValue = item[uniqueKey];
|
|
17449
|
+
if (uniqueKeyValue !== undefined && scopeMap.has(uniqueKeyValue)) {
|
|
17450
|
+
scope = scopeMap.get(uniqueKeyValue);
|
|
17451
|
+
break;
|
|
17452
|
+
}
|
|
17453
|
+
}
|
|
17454
|
+
}
|
|
17455
|
+
if (!scope) {
|
|
17456
|
+
scope = createScope(ownerId);
|
|
17457
|
+
}
|
|
17458
|
+
// Register the scope under the id and every uniqueKey value so that
|
|
17459
|
+
// resolveOwnerId can address it whichever key a callback returns.
|
|
17460
|
+
scopeMap.set(ownerId, scope);
|
|
17331
17461
|
for (const uniqueKey of uniqueKeys) {
|
|
17332
17462
|
const uniqueKeyValue = item[uniqueKey];
|
|
17333
|
-
if (uniqueKeyValue !== undefined
|
|
17334
|
-
|
|
17335
|
-
break;
|
|
17463
|
+
if (uniqueKeyValue !== undefined) {
|
|
17464
|
+
scopeMap.set(uniqueKeyValue, scope);
|
|
17336
17465
|
}
|
|
17337
17466
|
}
|
|
17338
17467
|
}
|
|
17339
|
-
if (!scope) {
|
|
17340
|
-
scope = createScope(ownerId);
|
|
17341
|
-
}
|
|
17342
|
-
// Register the scope under the id and every uniqueKey value so that
|
|
17343
|
-
// resolveOwnerId can address it whichever key a callback returns.
|
|
17344
|
-
scopeMap.set(ownerId, scope);
|
|
17345
|
-
for (const uniqueKey of uniqueKeys) {
|
|
17346
|
-
const uniqueKeyValue = item[uniqueKey];
|
|
17347
|
-
if (uniqueKeyValue !== undefined) {
|
|
17348
|
-
scopeMap.set(uniqueKeyValue, scope);
|
|
17349
|
-
}
|
|
17350
|
-
}
|
|
17351
17468
|
|
|
17352
17469
|
const { childStore, idArraySignal } = scope;
|
|
17353
17470
|
const updateChildItemIdArray = createChildIdArrayUpdater(
|
|
@@ -17385,7 +17502,7 @@ const createResource = (
|
|
|
17385
17502
|
const createRestActionForScopedMany = (
|
|
17386
17503
|
verb,
|
|
17387
17504
|
callback,
|
|
17388
|
-
{ isMany, onActionComplete },
|
|
17505
|
+
{ isMany, onActionComplete, paramScope },
|
|
17389
17506
|
) => {
|
|
17390
17507
|
const childActionName = `${childName}.${verb}`;
|
|
17391
17508
|
return createAction(callback, {
|
|
@@ -17407,7 +17524,11 @@ const createResource = (
|
|
|
17407
17524
|
);
|
|
17408
17525
|
// Owner not in store yet: create the scope so actions can run before
|
|
17409
17526
|
// the parent item has been loaded (e.g. rows fetched before their table).
|
|
17410
|
-
|
|
17527
|
+
let scope = scopeMap.get(ownerId);
|
|
17528
|
+
if (!scope) {
|
|
17529
|
+
scope = createScope(ownerId);
|
|
17530
|
+
scopeMap.set(ownerId, scope);
|
|
17531
|
+
}
|
|
17411
17532
|
const { childStore, idArraySignal } = scope;
|
|
17412
17533
|
|
|
17413
17534
|
if (verb === "DELETE") {
|
|
@@ -17477,20 +17598,19 @@ const createResource = (
|
|
|
17477
17598
|
addItemSetup: childAddItemSetup,
|
|
17478
17599
|
createRestAction: createRestActionForScopedMany,
|
|
17479
17600
|
paramScope,
|
|
17480
|
-
rerunOn: scopedManyRerunOn
|
|
17601
|
+
rerunOn: resolveRerunOn(scopedManyRerunOn, rerunOn),
|
|
17481
17602
|
dependencies: scopedManyDependencies ?? dependencies,
|
|
17603
|
+
ownerName: name,
|
|
17604
|
+
// Declared on a scoped child, the scopes are kept per item and never
|
|
17605
|
+
// registered by id (see addItemSetup above): there is no owner key to
|
|
17606
|
+
// find one by.
|
|
17607
|
+
getChildStore: ownerName
|
|
17608
|
+
? undefined
|
|
17609
|
+
: (ownerKey) => scopeMap.get(ownerKey)?.childStore,
|
|
17610
|
+
// scopedOne needs no such dependency: its mutation result carries the
|
|
17611
|
+
// updated object directly.
|
|
17612
|
+
ownerDependency: { owner: stateFacade, propertyName },
|
|
17482
17613
|
});
|
|
17483
|
-
// When a scoped child collection is mutated (POST etc.), the parent GET must
|
|
17484
|
-
// re-fetch: the parent embeds the child array and only the backend knows the
|
|
17485
|
-
// new ordering. (scopedOne does not need this: the mutation result contains
|
|
17486
|
-
// the updated object directly.)
|
|
17487
|
-
resourceLifecycleManager.addDependency(
|
|
17488
|
-
childResource,
|
|
17489
|
-
stateFacade,
|
|
17490
|
-
propertyName,
|
|
17491
|
-
);
|
|
17492
|
-
childResource.getChildStore = (ownerKey) =>
|
|
17493
|
-
scopeMap.get(ownerKey)?.childStore;
|
|
17494
17614
|
return childResource;
|
|
17495
17615
|
};
|
|
17496
17616
|
|
|
@@ -17711,7 +17831,7 @@ const getLastGetItemId = (action) => {
|
|
|
17711
17831
|
};
|
|
17712
17832
|
|
|
17713
17833
|
// Captures the "file:line:column" of the user code that invoked the public
|
|
17714
|
-
// function (resource(), .one(), .many(),
|
|
17834
|
+
// function (resource(), .one(), .many(), .scopedOne(), …), so invalid-result
|
|
17715
17835
|
// errors can point at where the callbacks were declared, not at this file.
|
|
17716
17836
|
// Must be called directly from the public function: the stack offset accounts
|
|
17717
17837
|
// for exactly two frames (getCallerInfo → getDeclarationSite → public fn → user code).
|
|
@@ -17732,6 +17852,36 @@ ${originalActionName} source location: ${declarationSite}`,
|
|
|
17732
17852
|
};
|
|
17733
17853
|
};
|
|
17734
17854
|
|
|
17855
|
+
// A scoped child's item exists only inside its owner: two owners can each hold
|
|
17856
|
+
// one with the same id. The callbacks of a relation chained on it whose result
|
|
17857
|
+
// names that item do so by its id alone — `.one()`/`.many()` as the parent
|
|
17858
|
+
// object or id, `.scopedOne()`/`.scopedMany()` as the `ownerId` of
|
|
17859
|
+
// `[ownerId, …]` — so applied they would write the owner's store (`store` of a
|
|
17860
|
+
// scoped child) or the scope of another owner's item. They are refused rather
|
|
17861
|
+
// than routed: routing needs the owner in the result, a contract none of them
|
|
17862
|
+
// has.
|
|
17863
|
+
const refuseCallbacksNamingScopedItem = (
|
|
17864
|
+
relationLabel,
|
|
17865
|
+
callbacks,
|
|
17866
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
17867
|
+
) => {
|
|
17868
|
+
const declaredKeys = [];
|
|
17869
|
+
for (const key of Object.keys(callbacks)) {
|
|
17870
|
+
if (callbacks[key]) {
|
|
17871
|
+
declaredKeys.push(key);
|
|
17872
|
+
}
|
|
17873
|
+
}
|
|
17874
|
+
if (declaredKeys.length === 0) {
|
|
17875
|
+
return;
|
|
17876
|
+
}
|
|
17877
|
+
const declared = declaredKeys.join(", ");
|
|
17878
|
+
throw new Error(
|
|
17879
|
+
`${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.
|
|
17880
|
+
Return "${propertyName}" embedded in what the "${name}" callbacks return ([ownerId, { …, ${propertyName} }]): those say which "${ownerName}".
|
|
17881
|
+
${relationLabel} source location: ${declarationSite}`,
|
|
17882
|
+
);
|
|
17883
|
+
};
|
|
17884
|
+
|
|
17735
17885
|
// Shared by .many() and .scopedMany(): converts a raw relationship value (an
|
|
17736
17886
|
// array of child props/ids, or anything else meaning "empty") into an array of
|
|
17737
17887
|
// child ids, upserting each entry into the child store. The id array signal is
|
|
@@ -17873,22 +18023,28 @@ Received an object with keys: ${keys.join(", ")}.`,
|
|
|
17873
18023
|
* // which in turn triggers the route Signal->URL sync and updates the browser URL.
|
|
17874
18024
|
*/
|
|
17875
18025
|
const syncResourceToSignals = (resource, propertyToSignalMap) => {
|
|
17876
|
-
if (resource.
|
|
18026
|
+
if (resource.ownerName) {
|
|
18027
|
+
// A scoped child's `store` is its owner's: watching it would sync the
|
|
18028
|
+
// owner items' properties, not the child's.
|
|
17877
18029
|
throw new Error(
|
|
17878
|
-
`syncResourceToSignals: "${resource.name}"
|
|
18030
|
+
`syncResourceToSignals: "${resource.name}" lives inside each "${resource.ownerName}", so it has no store of its own to watch.${resource.getChildStore ? " Use syncOwnedResourceToSignals instead." : ""}`,
|
|
17879
18031
|
);
|
|
17880
18032
|
}
|
|
17881
18033
|
syncStoreToSignals(resource.store, propertyToSignalMap);
|
|
17882
18034
|
};
|
|
17883
18035
|
|
|
17884
18036
|
/**
|
|
17885
|
-
* The same, for a `
|
|
17886
|
-
* per owner, so the store to watch is the one of the owner
|
|
17887
|
-
* (by id or by any unique key), and it is switched when the
|
|
17888
|
-
* Nothing is synced while `ownerSignal` holds `null`/`undefined`
|
|
17889
|
-
* owner nothing has been loaded for yet.
|
|
18037
|
+
* The same, for a `scopedMany` resource (or a `withParams()` of one): its items
|
|
18038
|
+
* live in one store per owner, so the store to watch is the one of the owner
|
|
18039
|
+
* `ownerSignal` names (by id or by any unique key), and it is switched when the
|
|
18040
|
+
* signal changes. Nothing is synced while `ownerSignal` holds `null`/`undefined`
|
|
18041
|
+
* or names an owner nothing has been loaded for yet.
|
|
17890
18042
|
*
|
|
17891
|
-
*
|
|
18043
|
+
* A `scopedOne` child has no store (each owner holds its one item), and a
|
|
18044
|
+
* `scopedMany` declared on a scoped child is kept per item with no owner key to
|
|
18045
|
+
* find it by: both are refused.
|
|
18046
|
+
*
|
|
18047
|
+
* @param {Object} resource - a resource made by `.scopedMany()`
|
|
17892
18048
|
* @param {import("@preact/signals").Signal} ownerSignal - the owner whose children are watched
|
|
17893
18049
|
* @param {Object} propertyToSignalMap - `{ [propertyName]: signal }`, as for `syncResourceToSignals`
|
|
17894
18050
|
*/
|
|
@@ -17898,8 +18054,13 @@ const syncOwnedResourceToSignals = (
|
|
|
17898
18054
|
propertyToSignalMap,
|
|
17899
18055
|
) => {
|
|
17900
18056
|
if (!resource.getChildStore) {
|
|
18057
|
+
if (!resource.ownerName) {
|
|
18058
|
+
throw new Error(
|
|
18059
|
+
`syncOwnedResourceToSignals: "${resource.name}" is not a scoped resource. Use syncResourceToSignals instead.`,
|
|
18060
|
+
);
|
|
18061
|
+
}
|
|
17901
18062
|
throw new Error(
|
|
17902
|
-
`syncOwnedResourceToSignals: "${resource.name}" is
|
|
18063
|
+
`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.`,
|
|
17903
18064
|
);
|
|
17904
18065
|
}
|
|
17905
18066
|
effect(() => {
|
|
@@ -20196,9 +20357,9 @@ const isSizeSpacingKey = (key) => {
|
|
|
20196
20357
|
// "vvw"/"vvh" are navi's own: the *visual* viewport, which — unlike vw/dvw —
|
|
20197
20358
|
// shrinks when the mobile virtual keyboard opens (see layout/responsive.js), so
|
|
20198
20359
|
// they are what a popup meant to stay clear of the keyboard should use.
|
|
20199
|
-
// "appw"/"apph" are the same thing narrowed to the app's own screen:
|
|
20200
|
-
//
|
|
20201
|
-
//
|
|
20360
|
+
// "appw"/"apph" are the same thing narrowed to the app's own screen: the
|
|
20361
|
+
// visual viewport minus the app's bands (--navi-app-inset-*), so identical to
|
|
20362
|
+
// vvw/vvh until the app declares bands. A gap meant to read as "a small margin" must use these —
|
|
20202
20363
|
// 3vvw on a 1500px window is a 45px gap around a 600px app.
|
|
20203
20364
|
// Functions rather than the signals themselves: appw/apph are not a signal to
|
|
20204
20365
|
// read but a value to compute (a signal, then a CSS var read back). Reading the
|
|
@@ -30194,7 +30355,7 @@ const TRANSITION_FURNITURE_CSS = /* css */ `.navi_fixed_bar[data-navi-transition
|
|
|
30194
30355
|
clip-path: var(--wall-holes-arriving, var(--wall-holes));
|
|
30195
30356
|
}
|
|
30196
30357
|
|
|
30197
|
-
:root[data-navi-route-transition] {
|
|
30358
|
+
:root[data-navi-route-transition-target="area"] {
|
|
30198
30359
|
& .navi_dialog::backdrop, & .navi_popover_backdrop {
|
|
30199
30360
|
backdrop-filter: none;
|
|
30200
30361
|
background: none;
|
|
@@ -30202,8 +30363,8 @@ const TRANSITION_FURNITURE_CSS = /* css */ `.navi_fixed_bar[data-navi-transition
|
|
|
30202
30363
|
}
|
|
30203
30364
|
`;
|
|
30204
30365
|
|
|
30205
|
-
// Called
|
|
30206
|
-
//
|
|
30366
|
+
// Called as a movement starts over a marked area, never at module scope: a
|
|
30367
|
+
// page that never travels between routes must not carry this sheet, and a
|
|
30207
30368
|
// build that sees no caller drops the css with the function.
|
|
30208
30369
|
const installTransitionFurnitureCss = () => {
|
|
30209
30370
|
import.meta.css = [TRANSITION_FURNITURE_CSS, "@jsenv/navi/src/nav/transition_furniture.js"];
|
|
@@ -30339,10 +30500,7 @@ const nameFurnitureAround = (areaElement) => {
|
|
|
30339
30500
|
if (!name) {
|
|
30340
30501
|
// A name the application wrote itself answers for that element, and it
|
|
30341
30502
|
// is saying something navi is not: it wants that bar moved on the pages'
|
|
30342
|
-
// clock, by its own CSS.
|
|
30343
|
-
// bar wearing one of ITS names is one this movement inherited from the
|
|
30344
|
-
// one it interrupted, and taking it for the application's would leave it
|
|
30345
|
-
// named for the rest of the document's life.
|
|
30503
|
+
// clock, by its own CSS.
|
|
30346
30504
|
if (getComputedStyle(element).viewTransitionName !== "none") {
|
|
30347
30505
|
continue;
|
|
30348
30506
|
}
|
|
@@ -30666,9 +30824,10 @@ const TRANSITION_WINDOW_CSS = /* css */ `@property --navi-transition-cover-top {
|
|
|
30666
30824
|
}
|
|
30667
30825
|
`;
|
|
30668
30826
|
|
|
30669
|
-
// Called
|
|
30670
|
-
// a page that never travels between routes must not
|
|
30671
|
-
// build that sees no caller drops the css with the
|
|
30827
|
+
// Called by whatever needs the window — a render, or a movement starting —
|
|
30828
|
+
// never at module scope: a page that never travels between routes must not
|
|
30829
|
+
// carry this sheet, and a build that sees no caller drops the css with the
|
|
30830
|
+
// function.
|
|
30672
30831
|
const installTransitionWindowCss = () => {
|
|
30673
30832
|
import.meta.css = [TRANSITION_WINDOW_CSS, "@jsenv/navi/src/nav/transition_window.js"];
|
|
30674
30833
|
};
|
|
@@ -31481,9 +31640,6 @@ const RouteTransitionArea = ({
|
|
|
31481
31640
|
children,
|
|
31482
31641
|
...rest
|
|
31483
31642
|
}) => {
|
|
31484
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
31485
|
-
installTransitionWindowCss();
|
|
31486
|
-
installTransitionFurnitureCss();
|
|
31487
31643
|
const props = {
|
|
31488
31644
|
...rest,
|
|
31489
31645
|
[TRANSITION_AREA_ATTRIBUTE]: ""
|
|
@@ -31552,8 +31708,6 @@ const RouteTransitionArea = ({
|
|
|
31552
31708
|
* @returns {() => void} remove this relation.
|
|
31553
31709
|
*/
|
|
31554
31710
|
const defineRouteTransition = (from, to, transition) => {
|
|
31555
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
31556
|
-
installTransitionWindowCss();
|
|
31557
31711
|
if (!to) {
|
|
31558
31712
|
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.`);
|
|
31559
31713
|
}
|
|
@@ -31593,8 +31747,6 @@ const defineRouteTransition = (from, to, transition) => {
|
|
|
31593
31747
|
* @returns {() => void} remove this default.
|
|
31594
31748
|
*/
|
|
31595
31749
|
const defineRouteDefaultTransition = transition => {
|
|
31596
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
31597
|
-
installTransitionWindowCss();
|
|
31598
31750
|
const value = normalizeTransition(transition);
|
|
31599
31751
|
defaultTransition = value;
|
|
31600
31752
|
return () => {
|
|
@@ -32170,12 +32322,25 @@ const beginTransition = ({
|
|
|
32170
32322
|
url: absoluteUrl(url),
|
|
32171
32323
|
decision: navigationDecision,
|
|
32172
32324
|
walkHome: null,
|
|
32173
|
-
releaseReverting: null
|
|
32325
|
+
releaseReverting: null,
|
|
32326
|
+
restoreDuration: null
|
|
32174
32327
|
};
|
|
32328
|
+
// The document wears one transition at a time, and the one this interrupts
|
|
32329
|
+
// ends only after this one has begun — too late to take off what it wears
|
|
32330
|
+
// (see end). Taking over therefore starts by taking it off, so that what is
|
|
32331
|
+
// put on below describes this transition alone, including what it leaves
|
|
32332
|
+
// unset: a type, an area, a duration.
|
|
32333
|
+
if (currentTransition) {
|
|
32334
|
+
releaseTransitionRoot(currentTransition);
|
|
32335
|
+
}
|
|
32175
32336
|
currentTransition = transition;
|
|
32176
32337
|
// Said before the picture is taken: whoever names something for a movement
|
|
32177
32338
|
// between two pages decides on it now (see transition_destination.js).
|
|
32178
32339
|
holdTransitionDestination(transition, url);
|
|
32340
|
+
// Adopted by what starts a movement rather than by what declares one: a link
|
|
32341
|
+
// or a navTo() asks for a movement in an app that declared none, and an app
|
|
32342
|
+
// that never moves never carries the sheet.
|
|
32343
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
32179
32344
|
documentElement.setAttribute(TRANSITION_ATTRIBUTE, direction);
|
|
32180
32345
|
if (type) {
|
|
32181
32346
|
documentElement.setAttribute(TRANSITION_TYPE_ATTRIBUTE, type);
|
|
@@ -32197,6 +32362,10 @@ const beginTransition = ({
|
|
|
32197
32362
|
// left (see transition_window.js).
|
|
32198
32363
|
let areaStateBefore = null;
|
|
32199
32364
|
if (areaElement) {
|
|
32365
|
+
// The area is the application's element as often as <RouteTransitionArea>,
|
|
32366
|
+
// so what a movement on it plays with is adopted here, where it is found.
|
|
32367
|
+
installTransitionWindowCss();
|
|
32368
|
+
installTransitionFurnitureCss();
|
|
32200
32369
|
documentElement.setAttribute(TRANSITION_TARGET_ATTRIBUTE, "area");
|
|
32201
32370
|
// Said before the picture is taken, like every name (see
|
|
32202
32371
|
// transition_furniture.js): what the bars are wearing when the transition
|
|
@@ -32207,11 +32376,10 @@ const beginTransition = ({
|
|
|
32207
32376
|
// A duration of this relation's own, worn for the length of the transition —
|
|
32208
32377
|
// and whatever the application had written inline put back afterwards, not
|
|
32209
32378
|
// erased.
|
|
32210
|
-
let restoreDuration = null;
|
|
32211
32379
|
if (duration !== undefined) {
|
|
32212
32380
|
const durationBefore = documentElement.style.getPropertyValue(TRANSITION_DURATION_PROPERTY);
|
|
32213
32381
|
documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, typeof duration === "number" ? `${duration}ms` : duration);
|
|
32214
|
-
restoreDuration = () => {
|
|
32382
|
+
transition.restoreDuration = () => {
|
|
32215
32383
|
if (durationBefore) {
|
|
32216
32384
|
documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, durationBefore);
|
|
32217
32385
|
} else {
|
|
@@ -32272,8 +32440,11 @@ const beginTransition = ({
|
|
|
32272
32440
|
renderWait.stop();
|
|
32273
32441
|
}
|
|
32274
32442
|
// The page arriving is in the DOM and the transition has not started
|
|
32275
|
-
// playing: the one moment both states of the area can be known.
|
|
32276
|
-
|
|
32443
|
+
// playing: the one moment both states of the area can be known. The
|
|
32444
|
+
// browser runs this callback even for a transition skipped before its
|
|
32445
|
+
// first picture — the next one starting skips it — and a transition
|
|
32446
|
+
// replaced holds nothing: what it wore was taken off at the takeover.
|
|
32447
|
+
if (areaElement && currentTransition === transition) {
|
|
32277
32448
|
holdTransitionFurniture(transition, areaElement);
|
|
32278
32449
|
holdTransitionWindow(transition, areaElement, areaStateBefore);
|
|
32279
32450
|
}
|
|
@@ -32282,8 +32453,9 @@ const beginTransition = ({
|
|
|
32282
32453
|
// Whatever ends it — played out, skipped by another transition starting,
|
|
32283
32454
|
// failed before its callback ever ran — the hold is given back and the
|
|
32284
32455
|
// document is handed back to the application. Both are idempotent, and
|
|
32285
|
-
// the
|
|
32286
|
-
// ending late
|
|
32456
|
+
// the document is handed back only by the LAST transition begun: an
|
|
32457
|
+
// earlier one ending late was taken off by the one that replaced it, and
|
|
32458
|
+
// must not strip what that one is wearing.
|
|
32287
32459
|
renderWait.stop();
|
|
32288
32460
|
releaseRendering();
|
|
32289
32461
|
// The hold a way back took, when it is still standing: the pictures were
|
|
@@ -32295,16 +32467,7 @@ const beginTransition = ({
|
|
|
32295
32467
|
}
|
|
32296
32468
|
if (currentTransition === transition) {
|
|
32297
32469
|
currentTransition = null;
|
|
32298
|
-
|
|
32299
|
-
documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
|
|
32300
|
-
documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
|
|
32301
|
-
releaseTransitionWindow(transition);
|
|
32302
|
-
releaseTransitionDestination(transition);
|
|
32303
|
-
releaseTransitionFurniture(transition);
|
|
32304
|
-
releaseTransitionPress(transition);
|
|
32305
|
-
if (restoreDuration) {
|
|
32306
|
-
restoreDuration();
|
|
32307
|
-
}
|
|
32470
|
+
releaseTransitionRoot(transition);
|
|
32308
32471
|
}
|
|
32309
32472
|
};
|
|
32310
32473
|
transition.viewTransition = viewTransition;
|
|
@@ -32312,6 +32475,22 @@ const beginTransition = ({
|
|
|
32312
32475
|
viewTransition.finished.then(end, end);
|
|
32313
32476
|
};
|
|
32314
32477
|
|
|
32478
|
+
// Everything a transition wears on the document for its length, taken off by
|
|
32479
|
+
// its own end or by the transition taking over from it.
|
|
32480
|
+
const releaseTransitionRoot = transition => {
|
|
32481
|
+
const documentElement = document.documentElement;
|
|
32482
|
+
documentElement.removeAttribute(TRANSITION_ATTRIBUTE);
|
|
32483
|
+
documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
|
|
32484
|
+
documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
|
|
32485
|
+
releaseTransitionWindow(transition);
|
|
32486
|
+
releaseTransitionDestination(transition);
|
|
32487
|
+
releaseTransitionFurniture(transition);
|
|
32488
|
+
releaseTransitionPress(transition);
|
|
32489
|
+
if (transition.restoreDuration) {
|
|
32490
|
+
transition.restoreDuration();
|
|
32491
|
+
}
|
|
32492
|
+
};
|
|
32493
|
+
|
|
32315
32494
|
/**
|
|
32316
32495
|
* The movement playing, turned round rather than replaced: this navigation is
|
|
32317
32496
|
* exactly the way back of the one on screen — the door pressed again while the
|
|
@@ -45400,16 +45579,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
45400
45579
|
border-radius: .2em;
|
|
45401
45580
|
}
|
|
45402
45581
|
|
|
45403
|
-
&[data-capitalize] {
|
|
45404
|
-
text-transform: capitalize;
|
|
45405
|
-
|
|
45406
|
-
& .navi_text_sizer {
|
|
45407
|
-
& .navi_text {
|
|
45408
|
-
display: inline-block;
|
|
45409
|
-
}
|
|
45410
|
-
}
|
|
45411
|
-
}
|
|
45412
|
-
|
|
45413
45582
|
&[data-shrinkwrap] {
|
|
45414
45583
|
display: inline-block;
|
|
45415
45584
|
}
|
|
@@ -45432,12 +45601,6 @@ time.navi_text {
|
|
|
45432
45601
|
}
|
|
45433
45602
|
|
|
45434
45603
|
.navi_text {
|
|
45435
|
-
&[data-capitalize] {
|
|
45436
|
-
&:first-letter, & .navi_text_sizer_placeholder:first-letter, & .navi_text_sizer_overlay:first-letter {
|
|
45437
|
-
text-transform: uppercase;
|
|
45438
|
-
}
|
|
45439
|
-
}
|
|
45440
|
-
|
|
45441
45604
|
& .navi_text_sizer, & .navi_text_sizer_placeholder, & .navi_text_sizer_overlay {
|
|
45442
45605
|
display: inherit;
|
|
45443
45606
|
width: inherit;
|
|
@@ -45885,7 +46048,9 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
45885
46048
|
* text block from being wider than its content when inside a flex/grid container.
|
|
45886
46049
|
*
|
|
45887
46050
|
* @param {boolean} [capitalize]
|
|
45888
|
-
* Uppercases the first letter of the text
|
|
46051
|
+
* Uppercases the first letter of the text, and only that one: "lundi 11 mai"
|
|
46052
|
+
* reads "Lundi 11 mai". Applied to the string the text starts with; a text
|
|
46053
|
+
* starting with an element keeps its first letter as written.
|
|
45889
46054
|
*
|
|
45890
46055
|
* @param {string|[number,number]|[string,string]} [selectRange]
|
|
45891
46056
|
* Selects a portion of the text on mount. Pass a substring to search for, a
|
|
@@ -46005,11 +46170,13 @@ const TextUI = props => {
|
|
|
46005
46170
|
shrinkWrap,
|
|
46006
46171
|
...rest
|
|
46007
46172
|
} = props;
|
|
46173
|
+
if (capitalize) {
|
|
46174
|
+
children = capitalizeFirstLetter(children);
|
|
46175
|
+
}
|
|
46008
46176
|
const defaultSpace = preventSpaceUnderlines ? FAKE_SPACE : REGULAR_SPACE;
|
|
46009
46177
|
const resolvedSpacing = spacing ?? defaultSpace;
|
|
46010
46178
|
const boxProps = {
|
|
46011
46179
|
"as": "span",
|
|
46012
|
-
"data-capitalize": capitalize ? "" : undefined,
|
|
46013
46180
|
"data-shrinkwrap": shrinkWrap ? "" : undefined,
|
|
46014
46181
|
...rest,
|
|
46015
46182
|
ref,
|
|
@@ -46310,6 +46477,28 @@ const Icon = ({
|
|
|
46310
46477
|
});
|
|
46311
46478
|
};
|
|
46312
46479
|
|
|
46480
|
+
// On the string rather than with ::first-letter, which needs a block container:
|
|
46481
|
+
// an inline text made one is an atomic box, and the ellipsis of the line
|
|
46482
|
+
// holding it (a Picker's value) can then no longer cut it. The string is also
|
|
46483
|
+
// what every copy of the children (sizer, bold background) renders, so they
|
|
46484
|
+
// all read the same. Leading spaces and punctuation are skipped, and a text
|
|
46485
|
+
// starting with a digit is left as it is, as ::first-letter would.
|
|
46486
|
+
const capitalizeFirstLetter = children => {
|
|
46487
|
+
if (typeof children === "string") {
|
|
46488
|
+
return children.replace(FIRST_LOWERCASE_LETTER_REGEXP, (match, lead, letter) => `${lead}${letter.toLocaleUpperCase()}`);
|
|
46489
|
+
}
|
|
46490
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
46491
|
+
const first = children[0];
|
|
46492
|
+
const firstCapitalized = capitalizeFirstLetter(first);
|
|
46493
|
+
if (firstCapitalized === first) {
|
|
46494
|
+
return children;
|
|
46495
|
+
}
|
|
46496
|
+
return [firstCapitalized, ...children.slice(1)];
|
|
46497
|
+
}
|
|
46498
|
+
return children;
|
|
46499
|
+
};
|
|
46500
|
+
const FIRST_LOWERCASE_LETTER_REGEXP = /^([\s\p{P}]*)(\p{Ll})/u;
|
|
46501
|
+
|
|
46313
46502
|
/**
|
|
46314
46503
|
* Where a link leads, asked for when the pointer or the focus arrives on it:
|
|
46315
46504
|
* the moment the user signals a destination, before the press. Only what the
|
|
@@ -61246,7 +61435,6 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
61246
61435
|
panelEl.style.transform = "";
|
|
61247
61436
|
panelEl.style.clipPath = "";
|
|
61248
61437
|
panelEl.style.transitionProperty = "";
|
|
61249
|
-
panelEl.style.userSelect = "";
|
|
61250
61438
|
};
|
|
61251
61439
|
// The resting value is written first and the animation only covers the way
|
|
61252
61440
|
// to it: both end on the same number, so nothing is seen changing when the
|
|
@@ -61290,10 +61478,6 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
61290
61478
|
return false;
|
|
61291
61479
|
}
|
|
61292
61480
|
panelEl.style.transitionProperty = "none";
|
|
61293
|
-
panelEl.style.userSelect = "none";
|
|
61294
|
-
// What the first pixels of the gesture may have started selecting is
|
|
61295
|
-
// not a selection, it is the beginning of this travel.
|
|
61296
|
-
window.getSelection().removeAllRanges();
|
|
61297
61481
|
// The way out is the only way there is anything: pulling the other way
|
|
61298
61482
|
// leans against a wall and comes back.
|
|
61299
61483
|
return {
|
|
@@ -61999,7 +62183,7 @@ const css$E = /* css */`
|
|
|
61999
62183
|
--navi-app-width rather than 3vvw): the gap must read as a small
|
|
62000
62184
|
margin around the dialog, and 3% of a 1500px window is a 45px gap
|
|
62001
62185
|
around a 600px app. Identical to 3vvw until the app declares
|
|
62002
|
-
--navi-app-max-width. */
|
|
62186
|
+
bands (--navi-app-max-width, or --navi-app-inset-* written by it). */
|
|
62003
62187
|
--x-dialog-container-spacing: calc(0.03 * var(--navi-app-width));
|
|
62004
62188
|
|
|
62005
62189
|
/* --navi-app-width, not --navi-vvw: a top-layer dialog is calibrated on
|
|
@@ -62736,8 +62920,8 @@ const css$E = /* css */`
|
|
|
62736
62920
|
* `positionArea`: it both caps the dialog's own size (via
|
|
62737
62921
|
* `--x-dialog-container-spacing`, written from this prop) and offsets a docked
|
|
62738
62922
|
* one from the edge it docks to. Accepts a number of pixels, a viewport
|
|
62739
|
-
* length — "appw"/"apph" being the app's own screen (the visual viewport
|
|
62740
|
-
*
|
|
62923
|
+
* length — "appw"/"apph" being the app's own screen (the visual viewport
|
|
62924
|
+
* minus the app's bands, see docs/safe_area.md) and
|
|
62741
62925
|
* "vvw"/"vvh" the visual viewport itself, which shrinks when the mobile
|
|
62742
62926
|
* keyboard opens — or a container length ("3cqw", the `layer="local"`
|
|
62743
62927
|
* default). Pass 0 for a dialog meant to sit flush (a side panel).
|
|
@@ -65881,6 +66065,8 @@ const css$C = /* css */`@layer navi {
|
|
|
65881
66065
|
* `maxWidth` below.
|
|
65882
66066
|
* @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
|
|
65883
66067
|
* @param {"box"|"scene"} [props.lift] - **Dialog-only**, same guard.
|
|
66068
|
+
* @param {Element|{current: Element}|string} [props.liftAnchor] -
|
|
66069
|
+
* **Dialog-only**, same guard.
|
|
65884
66070
|
* @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
|
|
65885
66071
|
* `Popover` have different own defaults (`"center"` vs. `"bottom"`),
|
|
65886
66072
|
* deliberately not homogenized here (each reads best for its own typical
|
|
@@ -65914,8 +66100,13 @@ const css$C = /* css */`@layer navi {
|
|
|
65914
66100
|
* identically): the wash the backdrop paints over what is behind.
|
|
65915
66101
|
* @param {string} [props.backdropFilter] - Forwarded as-is: what that wash
|
|
65916
66102
|
* does to the picture underneath, `"blur(4px)"` and the like.
|
|
65917
|
-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"
|
|
65918
|
-
* - Forwarded
|
|
66103
|
+
* @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]
|
|
66104
|
+
* - Forwarded to whichever renders — see either component's own doc.
|
|
66105
|
+
* `"expanding"`/`"expand-*"` are `Popover`'s own; `"lifting"` and
|
|
66106
|
+
* `{ open, close: "lifting" }` are `Dialog`'s. A popover has no lift, so in
|
|
66107
|
+
* popover mode `"lifting"` plays no movement (what a dialog does with no
|
|
66108
|
+
* anchor to lift out of) and the object form opens with its `open`, which
|
|
66109
|
+
* the close plays backwards like every other kind.
|
|
65919
66110
|
* @param {string} [props.animationDuration] - Forwarded as-is.
|
|
65920
66111
|
* @param {string} [props.maxWidth] - Forwarded as-is to both; also read
|
|
65921
66112
|
* here directly to help decide the automatic `mode` (a fixed length under
|
|
@@ -65981,6 +66172,8 @@ const Popup = props => {
|
|
|
65981
66172
|
// mode the automatic screen-size resolution happens to pick.
|
|
65982
66173
|
pressOutside = "close",
|
|
65983
66174
|
backdrop,
|
|
66175
|
+
// A popover is handed only the values it understands (toPopoverAnimation).
|
|
66176
|
+
animation,
|
|
65984
66177
|
// Popover-only (see this component's own doc) — destructured out so
|
|
65985
66178
|
// they're never part of ...rest, and therefore never forwarded to
|
|
65986
66179
|
// Dialog below, where they'd otherwise leak onto the real <dialog>
|
|
@@ -65995,6 +66188,7 @@ const Popup = props => {
|
|
|
65995
66188
|
dockedOnSmallTouchScreen,
|
|
65996
66189
|
sizeFromAnchor,
|
|
65997
66190
|
lift,
|
|
66191
|
+
liftAnchor,
|
|
65998
66192
|
...rest
|
|
65999
66193
|
} = props;
|
|
66000
66194
|
const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
|
|
@@ -66014,6 +66208,8 @@ const Popup = props => {
|
|
|
66014
66208
|
dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
|
|
66015
66209
|
sizeFromAnchor: sizeFromAnchor,
|
|
66016
66210
|
lift: lift,
|
|
66211
|
+
liftAnchor: liftAnchor,
|
|
66212
|
+
animation: animation,
|
|
66017
66213
|
maxWidth: maxWidth,
|
|
66018
66214
|
pressOutside: pressOutside,
|
|
66019
66215
|
backdrop: backdrop,
|
|
@@ -66027,6 +66223,7 @@ const Popup = props => {
|
|
|
66027
66223
|
}
|
|
66028
66224
|
return jsx(Popover, {
|
|
66029
66225
|
...rest,
|
|
66226
|
+
animation: toPopoverAnimation(animation),
|
|
66030
66227
|
maxWidth: maxWidth,
|
|
66031
66228
|
pressOutside: pressOutside,
|
|
66032
66229
|
backdrop: backdrop,
|
|
@@ -66044,6 +66241,18 @@ const Popup = props => {
|
|
|
66044
66241
|
};
|
|
66045
66242
|
Popup.Close = PopupClose;
|
|
66046
66243
|
|
|
66244
|
+
// What Dialog's own animation values mean to a popover, which has no lift
|
|
66245
|
+
// (see the `animation` doc above).
|
|
66246
|
+
const toPopoverAnimation = animation => {
|
|
66247
|
+
if (animation === "lifting") {
|
|
66248
|
+
return undefined;
|
|
66249
|
+
}
|
|
66250
|
+
if (animation !== null && typeof animation === "object") {
|
|
66251
|
+
return animation.open;
|
|
66252
|
+
}
|
|
66253
|
+
return animation;
|
|
66254
|
+
};
|
|
66255
|
+
|
|
66047
66256
|
installImportMetaCssBuild(import.meta);
|
|
66048
66257
|
const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
66049
66258
|
display: none;
|
|
@@ -67368,9 +67577,14 @@ const PickerNaviMinute = props => {
|
|
|
67368
67577
|
step,
|
|
67369
67578
|
value
|
|
67370
67579
|
} = props;
|
|
67580
|
+
|
|
67581
|
+
// resolveInputProps has already run (PickerFirstResolver), so the type
|
|
67582
|
+
// handed on is one the picker renders as is — the text field a navi numeric
|
|
67583
|
+
// type resolves to, told what its value is.
|
|
67371
67584
|
return jsx(Next, {
|
|
67372
67585
|
...props,
|
|
67373
|
-
type: "
|
|
67586
|
+
type: "text",
|
|
67587
|
+
"navi-input-type": "minute",
|
|
67374
67588
|
children: jsxs(Box, {
|
|
67375
67589
|
flex: "y",
|
|
67376
67590
|
spacing: "s",
|
|
@@ -74077,11 +74291,15 @@ const PickerNaviTime = props => {
|
|
|
74077
74291
|
max = "23:30",
|
|
74078
74292
|
step
|
|
74079
74293
|
} = props;
|
|
74080
|
-
|
|
74294
|
+
// resolveInputProps has already run (PickerFirstResolver) and left navi_time
|
|
74295
|
+
// as written: what is handed on is a time input, whose step is in seconds.
|
|
74296
|
+
const hostStep = timeStringToSeconds(step);
|
|
74297
|
+
const stepSeconds = hostStep ?? 1800;
|
|
74081
74298
|
const slots = useMemo(() => generateTimeSlots(min, max, stepSeconds), [min, max, stepSeconds]);
|
|
74082
74299
|
return jsx(Next, {
|
|
74083
74300
|
...props,
|
|
74084
74301
|
type: "time",
|
|
74302
|
+
step: hostStep,
|
|
74085
74303
|
children: jsx(List, {
|
|
74086
74304
|
selectable: true,
|
|
74087
74305
|
command: "--navi-send",
|
|
@@ -76525,7 +76743,7 @@ const PickerFirstResolver = props => {
|
|
|
76525
76743
|
* `focusOnOpen` below.
|
|
76526
76744
|
*
|
|
76527
76745
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
76528
|
-
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_number" | "navi_percentage",
|
|
76746
|
+
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_minute" | "navi_number" | "navi_percentage",
|
|
76529
76747
|
* value?: any,
|
|
76530
76748
|
* defaultValue?: any,
|
|
76531
76749
|
* name?: string,
|
|
@@ -77127,6 +77345,10 @@ const warnOnUnknownPickerType = props => {
|
|
|
77127
77345
|
pickerTypeWarnedSet.add(type);
|
|
77128
77346
|
console.warn(`[navi] <Picker type="${type}"> — "${type}" is not a picker type. ` + `The picker holds a single text value instead, so an object given to it is written as "[object Object]". ` + `Types are: date, month, week, time, datetime, duration, color, file, text, array, object, confirm — ` + `"object" being the one for a popup holding several named controls.`);
|
|
77129
77347
|
};
|
|
77348
|
+
|
|
77349
|
+
// PickerFirstResolver comes before the presets so a preset reads the
|
|
77350
|
+
// min/max/step a bound signal fills in (resolveInputProps); a preset type
|
|
77351
|
+
// therefore has no row in NAVI_TYPE_DEFAULTS (resolve_input_props.js).
|
|
77130
77352
|
const PickerResolved = /*#__PURE__*/createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerConfirmResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
77131
77353
|
const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
77132
77354
|
Date: PickerDateUI,
|
|
@@ -88548,6 +88770,8 @@ const css$3 = /* css */`@layer navi {
|
|
|
88548
88770
|
}
|
|
88549
88771
|
|
|
88550
88772
|
.navi_card_layout {
|
|
88773
|
+
box-sizing: border-box;
|
|
88774
|
+
min-height: 100%;
|
|
88551
88775
|
padding-top: var(--layout-margin-top, var(--layout-margin-y, var(--layout-margin)));
|
|
88552
88776
|
padding-right: var(--layout-margin-right, var(--layout-margin-x, var(--layout-margin)));
|
|
88553
88777
|
padding-bottom: var(--layout-margin-bottom, var(--layout-margin-y, var(--layout-margin)));
|
|
@@ -88589,6 +88813,11 @@ const CardLayoutStyleCSSVars = {
|
|
|
88589
88813
|
minHeight: "--layout-min-height"
|
|
88590
88814
|
};
|
|
88591
88815
|
/**
|
|
88816
|
+
* The card is centered in the area both ways and is as wide as its content,
|
|
88817
|
+
* `minWidth` at least. Down the area, the layout takes at least the height of
|
|
88818
|
+
* what holds it, so how tall that area is is up to the page around it, as for
|
|
88819
|
+
* `ViewportLayout`.
|
|
88820
|
+
*
|
|
88592
88821
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
88593
88822
|
* alignX?: string,
|
|
88594
88823
|
* alignY?: string,
|
|
@@ -88618,6 +88847,11 @@ const CardLayout = ({
|
|
|
88618
88847
|
}) => {
|
|
88619
88848
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
88620
88849
|
return jsx(Box, {
|
|
88850
|
+
flex: true
|
|
88851
|
+
// The card's place in the area; alignX/alignY below are its content's.
|
|
88852
|
+
,
|
|
88853
|
+
alignX: "center",
|
|
88854
|
+
alignY: "center",
|
|
88621
88855
|
baseClassName: "navi_card_layout",
|
|
88622
88856
|
styleCSSVars: CardLayoutStyleCSSVars,
|
|
88623
88857
|
visualSelector: ".navi_card",
|