@jsenv/navi 0.29.389 → 0.29.391
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 +376 -135
- package/dist/dev/jsenv_navi.js.map +8 -8
- package/dist/jsenv_navi.js +376 -135
- package/dist/jsenv_navi.js.map +8 -8
- package/docs/drag_interactions.md +13 -5
- package/docs/pan_zoom.md +11 -4
- package/docs/resource.md +45 -1
- package/package.json +2 -2
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -4955,14 +4955,11 @@ const CONTROL_TYPE_BY_SIGNAL_TYPE = {
|
|
|
4955
4955
|
// become something plainer — it is what says the value is a number (see
|
|
4956
4956
|
// isNumberInput), and what lets constraint messages use domain-specific
|
|
4957
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.
|
|
4958
4962
|
const NAVI_TYPE_DEFAULTS = {
|
|
4959
|
-
navi_time: {
|
|
4960
|
-
"type": "time",
|
|
4961
|
-
"navi-input-type": "time",
|
|
4962
|
-
"min": 0,
|
|
4963
|
-
"max": 24 * 3600 - 1,
|
|
4964
|
-
"step": 1,
|
|
4965
|
-
},
|
|
4966
4963
|
navi_percentage: {
|
|
4967
4964
|
"type": "navi_number",
|
|
4968
4965
|
"navi-input-type": "percentage",
|
|
@@ -5024,7 +5021,6 @@ const isNumberInput = (type, naviInputType) => {
|
|
|
5024
5021
|
* Supported navi types and their targets:
|
|
5025
5022
|
* - `navi_percentage` → `navi_number` (0–100, step 1)
|
|
5026
5023
|
* - `navi_number` → `text` (inputMode="numeric", no spin buttons implied)
|
|
5027
|
-
* - `navi_time` → `time` (step in seconds)
|
|
5028
5024
|
*
|
|
5029
5025
|
* Standard HTML input types with formatters:
|
|
5030
5026
|
* - `date`, `month`, `week`, `time`, `datetime-local`, `datetime`:
|
|
@@ -16611,6 +16607,15 @@ const createResource = (
|
|
|
16611
16607
|
paramScope,
|
|
16612
16608
|
rerunOn,
|
|
16613
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,
|
|
16614
16619
|
},
|
|
16615
16620
|
) => {
|
|
16616
16621
|
const params = paramScope.params;
|
|
@@ -16636,6 +16641,8 @@ const createResource = (
|
|
|
16636
16641
|
// private but exposed for convenience
|
|
16637
16642
|
store,
|
|
16638
16643
|
addItemSetup,
|
|
16644
|
+
ownerName,
|
|
16645
|
+
getChildStore,
|
|
16639
16646
|
};
|
|
16640
16647
|
|
|
16641
16648
|
resourceLifecycleManager.registerResource(stateFacade, {
|
|
@@ -16644,6 +16651,17 @@ const createResource = (
|
|
|
16644
16651
|
dependencies,
|
|
16645
16652
|
uniqueKeys,
|
|
16646
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
|
+
}
|
|
16647
16665
|
const onActionComplete = (actionCompleted) => {
|
|
16648
16666
|
resourceLifecycleManager.onActionComplete(actionCompleted, {
|
|
16649
16667
|
resourceScope: stateFacade,
|
|
@@ -16656,6 +16674,10 @@ const createResource = (
|
|
|
16656
16674
|
* Actions from parameterized resources only trigger rerun/reset for other actions with
|
|
16657
16675
|
* identical parameters, preventing cross-contamination between different parameter sets.
|
|
16658
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
|
+
*
|
|
16659
16681
|
* @param {Object} params - Parameters to bind to all actions of this resource (required)
|
|
16660
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
|
|
16661
16683
|
* @returns {Object} A new resource instance with parameter-bound actions and isolated lifecycle
|
|
@@ -16681,7 +16703,6 @@ const createResource = (
|
|
|
16681
16703
|
paramsToInject,
|
|
16682
16704
|
{ dependencies: withParamsDeps, rerunOn: withParamsRerunOn } = {},
|
|
16683
16705
|
) => {
|
|
16684
|
-
const declarationSite = getDeclarationSite();
|
|
16685
16706
|
if (!paramsToInject || Object.keys(paramsToInject).length === 0) {
|
|
16686
16707
|
throw new Error(`resource(${name}).withParams() requires parameters`);
|
|
16687
16708
|
}
|
|
@@ -16689,11 +16710,6 @@ const createResource = (
|
|
|
16689
16710
|
? { ...params, ...paramsToInject }
|
|
16690
16711
|
: paramsToInject;
|
|
16691
16712
|
const resolvedParamScope = getParamScope(resolvedParams);
|
|
16692
|
-
const createRestActionWithParams = createRestActionFactoryForRoot(name, {
|
|
16693
|
-
idKey,
|
|
16694
|
-
store,
|
|
16695
|
-
declarationSite,
|
|
16696
|
-
});
|
|
16697
16713
|
return createResource(name, {
|
|
16698
16714
|
idKey,
|
|
16699
16715
|
uniqueKeys,
|
|
@@ -16701,10 +16717,16 @@ const createResource = (
|
|
|
16701
16717
|
restCallbacks,
|
|
16702
16718
|
store,
|
|
16703
16719
|
addItemSetup,
|
|
16704
|
-
|
|
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,
|
|
16705
16724
|
paramScope: resolvedParamScope,
|
|
16706
16725
|
rerunOn: resolveRerunOn(withParamsRerunOn, rerunOn),
|
|
16707
16726
|
dependencies: withParamsDeps ?? dependencies,
|
|
16727
|
+
ownerName,
|
|
16728
|
+
getChildStore,
|
|
16729
|
+
ownerDependency,
|
|
16708
16730
|
});
|
|
16709
16731
|
};
|
|
16710
16732
|
stateFacade.withParams = withParams;
|
|
@@ -16749,6 +16771,13 @@ const createResource = (
|
|
|
16749
16771
|
} = {},
|
|
16750
16772
|
) => {
|
|
16751
16773
|
const declarationSite = getDeclarationSite();
|
|
16774
|
+
if (ownerName) {
|
|
16775
|
+
refuseCallbacksNamingScopedItem(
|
|
16776
|
+
`${name}.one("${propertyName}")`,
|
|
16777
|
+
{ GET, PUT, DELETE },
|
|
16778
|
+
{ name, ownerName, propertyName, declarationSite },
|
|
16779
|
+
);
|
|
16780
|
+
}
|
|
16752
16781
|
const childName = `${name}.${propertyName}`;
|
|
16753
16782
|
const childIdKey = childResource.idKey;
|
|
16754
16783
|
const childStore = childResource.store;
|
|
@@ -16809,7 +16838,11 @@ const createResource = (
|
|
|
16809
16838
|
);
|
|
16810
16839
|
});
|
|
16811
16840
|
|
|
16812
|
-
const createRestActionForOne = (
|
|
16841
|
+
const createRestActionForOne = (
|
|
16842
|
+
verb,
|
|
16843
|
+
callback,
|
|
16844
|
+
{ onActionComplete, paramScope },
|
|
16845
|
+
) => {
|
|
16813
16846
|
const applyResultToValue =
|
|
16814
16847
|
verb === "DELETE"
|
|
16815
16848
|
? (itemIdOrItemProps) => {
|
|
@@ -16924,6 +16957,13 @@ const createResource = (
|
|
|
16924
16957
|
} = {},
|
|
16925
16958
|
) => {
|
|
16926
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
|
+
}
|
|
16927
16967
|
const childStore = childResource.store;
|
|
16928
16968
|
const childIdKey = childResource.idKey;
|
|
16929
16969
|
const childName = `${name}.${propertyName}`;
|
|
@@ -16955,21 +16995,23 @@ const createResource = (
|
|
|
16955
16995
|
const createRestActionForMany = (
|
|
16956
16996
|
verb,
|
|
16957
16997
|
callback,
|
|
16958
|
-
{ isMany, onActionComplete },
|
|
16998
|
+
{ isMany, onActionComplete, paramScope },
|
|
16959
16999
|
) => {
|
|
16960
17000
|
if (!isMany) {
|
|
16961
17001
|
return createRestActionAffectingOneItem(verb, callback, {
|
|
16962
17002
|
onActionComplete,
|
|
17003
|
+
paramScope,
|
|
16963
17004
|
});
|
|
16964
17005
|
}
|
|
16965
17006
|
return createRestActionAffectingManyItems(verb, callback, {
|
|
16966
17007
|
onActionComplete,
|
|
17008
|
+
paramScope,
|
|
16967
17009
|
});
|
|
16968
17010
|
};
|
|
16969
17011
|
const createRestActionAffectingOneItem = (
|
|
16970
17012
|
verb,
|
|
16971
17013
|
callback,
|
|
16972
|
-
{ onActionComplete },
|
|
17014
|
+
{ onActionComplete, paramScope },
|
|
16973
17015
|
) => {
|
|
16974
17016
|
const applyResultToValue =
|
|
16975
17017
|
verb === "DELETE"
|
|
@@ -17032,7 +17074,7 @@ const createResource = (
|
|
|
17032
17074
|
const createRestActionAffectingManyItems = (
|
|
17033
17075
|
verb,
|
|
17034
17076
|
callback,
|
|
17035
|
-
{ onActionComplete },
|
|
17077
|
+
{ onActionComplete, paramScope },
|
|
17036
17078
|
) => {
|
|
17037
17079
|
const applyResultToValue =
|
|
17038
17080
|
verb === "GET"
|
|
@@ -17160,7 +17202,12 @@ const createResource = (
|
|
|
17160
17202
|
* Mutations apply directly to the owner's signal, so the parent GET is never rerun.
|
|
17161
17203
|
*
|
|
17162
17204
|
* Returns the child relationship resource, itself chainable:
|
|
17163
|
-
* `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.
|
|
17164
17211
|
*
|
|
17165
17212
|
* @param {string} propertyName - property holding the sub-object on each owner item
|
|
17166
17213
|
* @param {Object} [restCallbacks] - `{ idKey, rerunOn, dependencies, GET, POST, PUT, PATCH, DELETE }`
|
|
@@ -17180,6 +17227,14 @@ const createResource = (
|
|
|
17180
17227
|
DELETE,
|
|
17181
17228
|
} = {},
|
|
17182
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
|
+
}
|
|
17183
17238
|
const childName = `${name}.${propertyName}`;
|
|
17184
17239
|
|
|
17185
17240
|
// Callbacks added by chained .one()/.many() on the child resource,
|
|
@@ -17209,7 +17264,12 @@ const createResource = (
|
|
|
17209
17264
|
childSignal.value = childItem; // first activation: null → childItem
|
|
17210
17265
|
}
|
|
17211
17266
|
};
|
|
17212
|
-
|
|
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
|
+
}
|
|
17213
17273
|
|
|
17214
17274
|
applyProps(ownerItem[propertyName]);
|
|
17215
17275
|
|
|
@@ -17221,7 +17281,7 @@ const createResource = (
|
|
|
17221
17281
|
const createRestActionForScopedOne = (
|
|
17222
17282
|
verb,
|
|
17223
17283
|
callback,
|
|
17224
|
-
{ onActionComplete },
|
|
17284
|
+
{ onActionComplete, paramScope },
|
|
17225
17285
|
) => {
|
|
17226
17286
|
const childActionName = `${childName}.${verb}`;
|
|
17227
17287
|
return createAction(callback, {
|
|
@@ -17269,6 +17329,7 @@ const createResource = (
|
|
|
17269
17329
|
paramScope,
|
|
17270
17330
|
rerunOn: resolveRerunOn(scopedOneRerunOn, rerunOn),
|
|
17271
17331
|
dependencies: scopedOneDependencies ?? dependencies,
|
|
17332
|
+
ownerName: name,
|
|
17272
17333
|
});
|
|
17273
17334
|
};
|
|
17274
17335
|
|
|
@@ -17296,7 +17357,14 @@ const createResource = (
|
|
|
17296
17357
|
* `propertyName`), and the child's own GET_MANY reruns per its `rerunOn`.
|
|
17297
17358
|
*
|
|
17298
17359
|
* Returns the child relationship resource, itself chainable:
|
|
17299
|
-
* `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.
|
|
17300
17368
|
*
|
|
17301
17369
|
* @param {string} propertyName - property holding the collection on each owner item
|
|
17302
17370
|
* @param {Object} [restCallbacks] - `{ idKey, rerunOn, dependencies, GET, GET_MANY, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
@@ -17321,6 +17389,25 @@ const createResource = (
|
|
|
17321
17389
|
DELETE_MANY,
|
|
17322
17390
|
} = {},
|
|
17323
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
|
+
}
|
|
17324
17411
|
const childName = `${name}.${propertyName}`;
|
|
17325
17412
|
|
|
17326
17413
|
// Callbacks added by chained .one()/.many() on the child resource,
|
|
@@ -17342,37 +17429,42 @@ const createResource = (
|
|
|
17342
17429
|
return childItem;
|
|
17343
17430
|
},
|
|
17344
17431
|
});
|
|
17345
|
-
|
|
17346
|
-
scopeMap.set(ownerKey, scope);
|
|
17347
|
-
return scope;
|
|
17432
|
+
return { childStore, idArraySignal: signal([]) };
|
|
17348
17433
|
};
|
|
17349
17434
|
addItemSetup((item) => {
|
|
17350
17435
|
const ownerId = item[idKey];
|
|
17351
|
-
|
|
17352
|
-
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
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);
|
|
17356
17461
|
for (const uniqueKey of uniqueKeys) {
|
|
17357
17462
|
const uniqueKeyValue = item[uniqueKey];
|
|
17358
|
-
if (uniqueKeyValue !== undefined
|
|
17359
|
-
|
|
17360
|
-
break;
|
|
17463
|
+
if (uniqueKeyValue !== undefined) {
|
|
17464
|
+
scopeMap.set(uniqueKeyValue, scope);
|
|
17361
17465
|
}
|
|
17362
17466
|
}
|
|
17363
17467
|
}
|
|
17364
|
-
if (!scope) {
|
|
17365
|
-
scope = createScope(ownerId);
|
|
17366
|
-
}
|
|
17367
|
-
// Register the scope under the id and every uniqueKey value so that
|
|
17368
|
-
// resolveOwnerId can address it whichever key a callback returns.
|
|
17369
|
-
scopeMap.set(ownerId, scope);
|
|
17370
|
-
for (const uniqueKey of uniqueKeys) {
|
|
17371
|
-
const uniqueKeyValue = item[uniqueKey];
|
|
17372
|
-
if (uniqueKeyValue !== undefined) {
|
|
17373
|
-
scopeMap.set(uniqueKeyValue, scope);
|
|
17374
|
-
}
|
|
17375
|
-
}
|
|
17376
17468
|
|
|
17377
17469
|
const { childStore, idArraySignal } = scope;
|
|
17378
17470
|
const updateChildItemIdArray = createChildIdArrayUpdater(
|
|
@@ -17410,7 +17502,7 @@ const createResource = (
|
|
|
17410
17502
|
const createRestActionForScopedMany = (
|
|
17411
17503
|
verb,
|
|
17412
17504
|
callback,
|
|
17413
|
-
{ isMany, onActionComplete },
|
|
17505
|
+
{ isMany, onActionComplete, paramScope },
|
|
17414
17506
|
) => {
|
|
17415
17507
|
const childActionName = `${childName}.${verb}`;
|
|
17416
17508
|
return createAction(callback, {
|
|
@@ -17432,7 +17524,11 @@ const createResource = (
|
|
|
17432
17524
|
);
|
|
17433
17525
|
// Owner not in store yet: create the scope so actions can run before
|
|
17434
17526
|
// the parent item has been loaded (e.g. rows fetched before their table).
|
|
17435
|
-
|
|
17527
|
+
let scope = scopeMap.get(ownerId);
|
|
17528
|
+
if (!scope) {
|
|
17529
|
+
scope = createScope(ownerId);
|
|
17530
|
+
scopeMap.set(ownerId, scope);
|
|
17531
|
+
}
|
|
17436
17532
|
const { childStore, idArraySignal } = scope;
|
|
17437
17533
|
|
|
17438
17534
|
if (verb === "DELETE") {
|
|
@@ -17504,18 +17600,17 @@ const createResource = (
|
|
|
17504
17600
|
paramScope,
|
|
17505
17601
|
rerunOn: resolveRerunOn(scopedManyRerunOn, rerunOn),
|
|
17506
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 },
|
|
17507
17613
|
});
|
|
17508
|
-
// When a scoped child collection is mutated (POST etc.), the parent GET must
|
|
17509
|
-
// re-fetch: the parent embeds the child array and only the backend knows the
|
|
17510
|
-
// new ordering. (scopedOne does not need this: the mutation result contains
|
|
17511
|
-
// the updated object directly.)
|
|
17512
|
-
resourceLifecycleManager.addDependency(
|
|
17513
|
-
childResource,
|
|
17514
|
-
stateFacade,
|
|
17515
|
-
propertyName,
|
|
17516
|
-
);
|
|
17517
|
-
childResource.getChildStore = (ownerKey) =>
|
|
17518
|
-
scopeMap.get(ownerKey)?.childStore;
|
|
17519
17614
|
return childResource;
|
|
17520
17615
|
};
|
|
17521
17616
|
|
|
@@ -17736,7 +17831,7 @@ const getLastGetItemId = (action) => {
|
|
|
17736
17831
|
};
|
|
17737
17832
|
|
|
17738
17833
|
// Captures the "file:line:column" of the user code that invoked the public
|
|
17739
|
-
// function (resource(), .one(), .many(),
|
|
17834
|
+
// function (resource(), .one(), .many(), .scopedOne(), …), so invalid-result
|
|
17740
17835
|
// errors can point at where the callbacks were declared, not at this file.
|
|
17741
17836
|
// Must be called directly from the public function: the stack offset accounts
|
|
17742
17837
|
// for exactly two frames (getCallerInfo → getDeclarationSite → public fn → user code).
|
|
@@ -17757,6 +17852,36 @@ ${originalActionName} source location: ${declarationSite}`,
|
|
|
17757
17852
|
};
|
|
17758
17853
|
};
|
|
17759
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
|
+
|
|
17760
17885
|
// Shared by .many() and .scopedMany(): converts a raw relationship value (an
|
|
17761
17886
|
// array of child props/ids, or anything else meaning "empty") into an array of
|
|
17762
17887
|
// child ids, upserting each entry into the child store. The id array signal is
|
|
@@ -17898,22 +18023,28 @@ Received an object with keys: ${keys.join(", ")}.`,
|
|
|
17898
18023
|
* // which in turn triggers the route Signal->URL sync and updates the browser URL.
|
|
17899
18024
|
*/
|
|
17900
18025
|
const syncResourceToSignals = (resource, propertyToSignalMap) => {
|
|
17901
|
-
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.
|
|
17902
18029
|
throw new Error(
|
|
17903
|
-
`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." : ""}`,
|
|
17904
18031
|
);
|
|
17905
18032
|
}
|
|
17906
18033
|
syncStoreToSignals(resource.store, propertyToSignalMap);
|
|
17907
18034
|
};
|
|
17908
18035
|
|
|
17909
18036
|
/**
|
|
17910
|
-
* The same, for a `
|
|
17911
|
-
* per owner, so the store to watch is the one of the owner
|
|
17912
|
-
* (by id or by any unique key), and it is switched when the
|
|
17913
|
-
* Nothing is synced while `ownerSignal` holds `null`/`undefined`
|
|
17914
|
-
* 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.
|
|
17915
18042
|
*
|
|
17916
|
-
*
|
|
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()`
|
|
17917
18048
|
* @param {import("@preact/signals").Signal} ownerSignal - the owner whose children are watched
|
|
17918
18049
|
* @param {Object} propertyToSignalMap - `{ [propertyName]: signal }`, as for `syncResourceToSignals`
|
|
17919
18050
|
*/
|
|
@@ -17923,8 +18054,13 @@ const syncOwnedResourceToSignals = (
|
|
|
17923
18054
|
propertyToSignalMap,
|
|
17924
18055
|
) => {
|
|
17925
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
|
+
}
|
|
17926
18062
|
throw new Error(
|
|
17927
|
-
`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.`,
|
|
17928
18064
|
);
|
|
17929
18065
|
}
|
|
17930
18066
|
effect(() => {
|
|
@@ -30219,7 +30355,7 @@ const TRANSITION_FURNITURE_CSS = /* css */ `.navi_fixed_bar[data-navi-transition
|
|
|
30219
30355
|
clip-path: var(--wall-holes-arriving, var(--wall-holes));
|
|
30220
30356
|
}
|
|
30221
30357
|
|
|
30222
|
-
:root[data-navi-route-transition] {
|
|
30358
|
+
:root[data-navi-route-transition-target="area"] {
|
|
30223
30359
|
& .navi_dialog::backdrop, & .navi_popover_backdrop {
|
|
30224
30360
|
backdrop-filter: none;
|
|
30225
30361
|
background: none;
|
|
@@ -30364,10 +30500,7 @@ const nameFurnitureAround = (areaElement) => {
|
|
|
30364
30500
|
if (!name) {
|
|
30365
30501
|
// A name the application wrote itself answers for that element, and it
|
|
30366
30502
|
// is saying something navi is not: it wants that bar moved on the pages'
|
|
30367
|
-
// clock, by its own CSS.
|
|
30368
|
-
// bar wearing one of ITS names is one this movement inherited from the
|
|
30369
|
-
// one it interrupted, and taking it for the application's would leave it
|
|
30370
|
-
// named for the rest of the document's life.
|
|
30503
|
+
// clock, by its own CSS.
|
|
30371
30504
|
if (getComputedStyle(element).viewTransitionName !== "none") {
|
|
30372
30505
|
continue;
|
|
30373
30506
|
}
|
|
@@ -32189,8 +32322,17 @@ const beginTransition = ({
|
|
|
32189
32322
|
url: absoluteUrl(url),
|
|
32190
32323
|
decision: navigationDecision,
|
|
32191
32324
|
walkHome: null,
|
|
32192
|
-
releaseReverting: null
|
|
32325
|
+
releaseReverting: null,
|
|
32326
|
+
restoreDuration: null
|
|
32193
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
|
+
}
|
|
32194
32336
|
currentTransition = transition;
|
|
32195
32337
|
// Said before the picture is taken: whoever names something for a movement
|
|
32196
32338
|
// between two pages decides on it now (see transition_destination.js).
|
|
@@ -32234,11 +32376,10 @@ const beginTransition = ({
|
|
|
32234
32376
|
// A duration of this relation's own, worn for the length of the transition —
|
|
32235
32377
|
// and whatever the application had written inline put back afterwards, not
|
|
32236
32378
|
// erased.
|
|
32237
|
-
let restoreDuration = null;
|
|
32238
32379
|
if (duration !== undefined) {
|
|
32239
32380
|
const durationBefore = documentElement.style.getPropertyValue(TRANSITION_DURATION_PROPERTY);
|
|
32240
32381
|
documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, typeof duration === "number" ? `${duration}ms` : duration);
|
|
32241
|
-
restoreDuration = () => {
|
|
32382
|
+
transition.restoreDuration = () => {
|
|
32242
32383
|
if (durationBefore) {
|
|
32243
32384
|
documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, durationBefore);
|
|
32244
32385
|
} else {
|
|
@@ -32299,8 +32440,11 @@ const beginTransition = ({
|
|
|
32299
32440
|
renderWait.stop();
|
|
32300
32441
|
}
|
|
32301
32442
|
// The page arriving is in the DOM and the transition has not started
|
|
32302
|
-
// playing: the one moment both states of the area can be known.
|
|
32303
|
-
|
|
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) {
|
|
32304
32448
|
holdTransitionFurniture(transition, areaElement);
|
|
32305
32449
|
holdTransitionWindow(transition, areaElement, areaStateBefore);
|
|
32306
32450
|
}
|
|
@@ -32309,8 +32453,9 @@ const beginTransition = ({
|
|
|
32309
32453
|
// Whatever ends it — played out, skipped by another transition starting,
|
|
32310
32454
|
// failed before its callback ever ran — the hold is given back and the
|
|
32311
32455
|
// document is handed back to the application. Both are idempotent, and
|
|
32312
|
-
// the
|
|
32313
|
-
// 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.
|
|
32314
32459
|
renderWait.stop();
|
|
32315
32460
|
releaseRendering();
|
|
32316
32461
|
// The hold a way back took, when it is still standing: the pictures were
|
|
@@ -32322,16 +32467,7 @@ const beginTransition = ({
|
|
|
32322
32467
|
}
|
|
32323
32468
|
if (currentTransition === transition) {
|
|
32324
32469
|
currentTransition = null;
|
|
32325
|
-
|
|
32326
|
-
documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
|
|
32327
|
-
documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
|
|
32328
|
-
releaseTransitionWindow(transition);
|
|
32329
|
-
releaseTransitionDestination(transition);
|
|
32330
|
-
releaseTransitionFurniture(transition);
|
|
32331
|
-
releaseTransitionPress(transition);
|
|
32332
|
-
if (restoreDuration) {
|
|
32333
|
-
restoreDuration();
|
|
32334
|
-
}
|
|
32470
|
+
releaseTransitionRoot(transition);
|
|
32335
32471
|
}
|
|
32336
32472
|
};
|
|
32337
32473
|
transition.viewTransition = viewTransition;
|
|
@@ -32339,6 +32475,22 @@ const beginTransition = ({
|
|
|
32339
32475
|
viewTransition.finished.then(end, end);
|
|
32340
32476
|
};
|
|
32341
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
|
+
|
|
32342
32494
|
/**
|
|
32343
32495
|
* The movement playing, turned round rather than replaced: this navigation is
|
|
32344
32496
|
* exactly the way back of the one on screen — the door pressed again while the
|
|
@@ -50186,10 +50338,10 @@ const css$R = /* css */`.navi_expandable {
|
|
|
50186
50338
|
}
|
|
50187
50339
|
|
|
50188
50340
|
& > .navi_expandable_content_container {
|
|
50189
|
-
clip-path: inset(-9999px -9999px 0);
|
|
50190
50341
|
grid-template-rows: 0fr;
|
|
50191
50342
|
display: grid;
|
|
50192
50343
|
position: relative;
|
|
50344
|
+
overflow-y: clip;
|
|
50193
50345
|
|
|
50194
50346
|
& > .navi_expandable_content_sizer {
|
|
50195
50347
|
min-height: 0;
|
|
@@ -50202,7 +50354,6 @@ const css$R = /* css */`.navi_expandable {
|
|
|
50202
50354
|
}
|
|
50203
50355
|
|
|
50204
50356
|
&[data-open-direction="up"] > .navi_expandable_content_container {
|
|
50205
|
-
clip-path: inset(0 -9999px -9999px);
|
|
50206
50357
|
align-content: end;
|
|
50207
50358
|
|
|
50208
50359
|
& > .navi_expandable_content_sizer {
|
|
@@ -50224,9 +50375,9 @@ const css$R = /* css */`.navi_expandable {
|
|
|
50224
50375
|
}
|
|
50225
50376
|
|
|
50226
50377
|
& > .navi_expandable_content_container {
|
|
50227
|
-
clip-path: inset(-9999px 0 0 -9999px);
|
|
50228
50378
|
grid-template-rows: 0fr;
|
|
50229
50379
|
grid-template-columns: 0fr;
|
|
50380
|
+
overflow: clip;
|
|
50230
50381
|
|
|
50231
50382
|
& > .navi_expandable_content_sizer {
|
|
50232
50383
|
min-width: 0;
|
|
@@ -50244,7 +50395,6 @@ const css$R = /* css */`.navi_expandable {
|
|
|
50244
50395
|
}
|
|
50245
50396
|
|
|
50246
50397
|
&[data-open-direction="left"] > .navi_expandable_content_container {
|
|
50247
|
-
clip-path: inset(-9999px -9999px 0 0);
|
|
50248
50398
|
justify-content: end;
|
|
50249
50399
|
|
|
50250
50400
|
& > .navi_expandable_content_sizer {
|
|
@@ -50266,11 +50416,10 @@ const css$R = /* css */`.navi_expandable {
|
|
|
50266
50416
|
}
|
|
50267
50417
|
|
|
50268
50418
|
&[aria-expanded="true"][data-settled] > .navi_expandable_content_container {
|
|
50269
|
-
|
|
50419
|
+
overflow: visible;
|
|
50270
50420
|
}
|
|
50271
50421
|
|
|
50272
50422
|
&:not([aria-expanded="true"])[data-settled] > .navi_expandable_content_container {
|
|
50273
|
-
clip-path: none;
|
|
50274
50423
|
overflow: clip;
|
|
50275
50424
|
}
|
|
50276
50425
|
|
|
@@ -50367,11 +50516,12 @@ const useExpandableContext = partName => {
|
|
|
50367
50516
|
* that can scroll so the content revealed past its edge is on screen: an
|
|
50368
50517
|
* expandable opening at the bottom of a list otherwise grows below the
|
|
50369
50518
|
* fold, and the user sees the row grow but not what it grew with. Measured
|
|
50370
|
-
* on the open layout
|
|
50371
|
-
*
|
|
50372
|
-
*
|
|
50373
|
-
*
|
|
50374
|
-
*
|
|
50519
|
+
* on the open layout, once per opening; when `animation` plays the
|
|
50520
|
+
* scroller rides along with the reveal, at the track's own progress, and
|
|
50521
|
+
* otherwise it goes there at once. A `scroll-margin` on the expandable is
|
|
50522
|
+
* honored. An expandable taller than the scroller keeps its UI part on
|
|
50523
|
+
* screen and the content follows as far as it fits. Only the nearest
|
|
50524
|
+
* scroller moves — a popup or a page around it stays where it is.
|
|
50375
50525
|
* @param maxContentHeight - Caps the content height; taller content scrolls
|
|
50376
50526
|
* inside the expandable instead of growing it.
|
|
50377
50527
|
* @param mount - When the content is built and thrown away, a popup's own
|
|
@@ -50494,15 +50644,27 @@ const Expandable = props => {
|
|
|
50494
50644
|
cancelSettleWatchRef.current?.();
|
|
50495
50645
|
cancelSettleWatchRef.current = null;
|
|
50496
50646
|
};
|
|
50497
|
-
|
|
50647
|
+
// `stopRide`, when the scroller rides along with the reveal (keepInView):
|
|
50648
|
+
// the ride is part of the movement, so it ends with it — settled or
|
|
50649
|
+
// cancelled — and never on its own clock.
|
|
50650
|
+
const watchSettle = stopRide => {
|
|
50498
50651
|
const contentContainer = contentContainerRef.current;
|
|
50499
50652
|
const contentElement = contentContainer.firstElementChild.firstElementChild;
|
|
50500
|
-
|
|
50653
|
+
const cancelWatch = whenTransitionSettles(contentContainer, () => {
|
|
50501
50654
|
cancelSettleWatchRef.current = null;
|
|
50655
|
+
if (stopRide) {
|
|
50656
|
+
stopRide();
|
|
50657
|
+
}
|
|
50502
50658
|
contentElement.style.width = "";
|
|
50503
50659
|
contentElement.style.height = "";
|
|
50504
50660
|
setSettled(true);
|
|
50505
50661
|
});
|
|
50662
|
+
cancelSettleWatchRef.current = () => {
|
|
50663
|
+
if (stopRide) {
|
|
50664
|
+
stopRide();
|
|
50665
|
+
}
|
|
50666
|
+
cancelWatch();
|
|
50667
|
+
};
|
|
50506
50668
|
};
|
|
50507
50669
|
|
|
50508
50670
|
// The reveal needs the content at its final size before the track starts
|
|
@@ -50521,17 +50683,22 @@ const Expandable = props => {
|
|
|
50521
50683
|
} else {
|
|
50522
50684
|
contentElement.style.height = `${finalRect.height}px`;
|
|
50523
50685
|
}
|
|
50686
|
+
let stopRide = null;
|
|
50524
50687
|
if (keepInView) {
|
|
50525
|
-
//
|
|
50526
|
-
// this point, so where the content ends is known before the
|
|
50527
|
-
// starts
|
|
50528
|
-
//
|
|
50529
|
-
|
|
50530
|
-
|
|
50531
|
-
|
|
50532
|
-
|
|
50533
|
-
|
|
50534
|
-
|
|
50688
|
+
// Measured here and not once the reveal settles: the layout is the open
|
|
50689
|
+
// one at this point, so where the content ends is known before the
|
|
50690
|
+
// movement starts. The scroll then rides the movement rather than
|
|
50691
|
+
// running ahead of it (see rideScrollerAlongReveal).
|
|
50692
|
+
const revealScroll = measureRevealScroll(rootRef.current, revealDirection);
|
|
50693
|
+
if (revealScroll) {
|
|
50694
|
+
const finalContainerRect = contentContainer.getBoundingClientRect();
|
|
50695
|
+
stopRide = rideScrollerAlongReveal(revealScroll, {
|
|
50696
|
+
contentContainer,
|
|
50697
|
+
axis: isColumn ? "x" : "y",
|
|
50698
|
+
startSize: isColumn ? startRect.width : startRect.height,
|
|
50699
|
+
finalSize: isColumn ? finalContainerRect.width : finalContainerRect.height
|
|
50700
|
+
});
|
|
50701
|
+
}
|
|
50535
50702
|
}
|
|
50536
50703
|
// Put the tracks back where the last paint left them and let the
|
|
50537
50704
|
// transition play from there. In fr — px does not interpolate with fr.
|
|
@@ -50554,6 +50721,7 @@ const Expandable = props => {
|
|
|
50554
50721
|
contentContainer.style.transitionProperty = "";
|
|
50555
50722
|
contentContainer.style.gridTemplateColumns = "";
|
|
50556
50723
|
contentContainer.style.gridTemplateRows = "";
|
|
50724
|
+
return stopRide;
|
|
50557
50725
|
};
|
|
50558
50726
|
|
|
50559
50727
|
// What opening LOOKS like here, and how to undo it — the one thing an
|
|
@@ -50585,12 +50753,17 @@ const Expandable = props => {
|
|
|
50585
50753
|
// transfer there is nothing to restore on close either.
|
|
50586
50754
|
const restoreFocus = autoFocus && !silent && contentContainer ? openController.transferFocusOnOpen(contentContainer) : null;
|
|
50587
50755
|
if (revealing) {
|
|
50588
|
-
armReveal(startRect);
|
|
50589
|
-
watchSettle();
|
|
50756
|
+
const stopRide = armReveal(startRect);
|
|
50757
|
+
watchSettle(stopRide);
|
|
50590
50758
|
} else if (keepInView && !silent && contentContainer) {
|
|
50591
|
-
|
|
50592
|
-
|
|
50593
|
-
|
|
50759
|
+
const revealScroll = measureRevealScroll(rootRef.current, revealDirection);
|
|
50760
|
+
if (revealScroll) {
|
|
50761
|
+
// No behavior: the scroller's own scroll-behavior decides.
|
|
50762
|
+
revealScroll.scrollerEl.scrollTo({
|
|
50763
|
+
left: revealScroll.left,
|
|
50764
|
+
top: revealScroll.top
|
|
50765
|
+
});
|
|
50766
|
+
}
|
|
50594
50767
|
}
|
|
50595
50768
|
if (!silent) {
|
|
50596
50769
|
rootRef.current.dispatchEvent(createToggleEvent(true));
|
|
@@ -50632,7 +50805,7 @@ const Expandable = props => {
|
|
|
50632
50805
|
}
|
|
50633
50806
|
}
|
|
50634
50807
|
if (collapsing) {
|
|
50635
|
-
watchSettle();
|
|
50808
|
+
watchSettle(null);
|
|
50636
50809
|
}
|
|
50637
50810
|
rootRef.current.dispatchEvent(createToggleEvent(false));
|
|
50638
50811
|
};
|
|
@@ -50972,16 +51145,14 @@ Expandable.Content = ExpandableContent;
|
|
|
50972
51145
|
// the one exception, excluded at the call site.
|
|
50973
51146
|
const UI_INTERACTIVE_SELECTOR = ["a[href]", "button", "input", "select", "textarea", "label", "[role='button']", "[contenteditable='']", "[contenteditable='true']", "audio[controls]", "video[controls]"].join(", ");
|
|
50974
51147
|
|
|
50975
|
-
//
|
|
50976
|
-
// part and revealed content —
|
|
50977
|
-
// so the caller
|
|
50978
|
-
// openEffect). A root taller than the view is aligned on
|
|
50979
|
-
// what the user pressed stays on screen, and the content
|
|
50980
|
-
// fits. One scroller only — a popup or a page around it
|
|
50981
|
-
|
|
50982
|
-
|
|
50983
|
-
behavior
|
|
50984
|
-
}) => {
|
|
51148
|
+
// Where the nearest scroller that can scroll would have to be for the whole
|
|
51149
|
+
// expandable — UI part and revealed content — to be in view. Measured on the
|
|
51150
|
+
// layout as it stands, so the caller measures while the open layout is the
|
|
51151
|
+
// one laid out (see openEffect). A root taller than the view is aligned on
|
|
51152
|
+
// its UI side instead: what the user pressed stays on screen, and the content
|
|
51153
|
+
// follows as far as it fits. One scroller only — a popup or a page around it
|
|
51154
|
+
// is left where it is. Null when there is no scroller, or nothing to scroll.
|
|
51155
|
+
const measureRevealScroll = (rootEl, revealDirection) => {
|
|
50985
51156
|
let scrollerEl = null;
|
|
50986
51157
|
for (const scrollContainer of getScrollContainerSet(rootEl)) {
|
|
50987
51158
|
if (isScrollable(scrollContainer)) {
|
|
@@ -50990,7 +51161,7 @@ const scrollExpandableIntoView = (rootEl, {
|
|
|
50990
51161
|
}
|
|
50991
51162
|
}
|
|
50992
51163
|
if (!scrollerEl) {
|
|
50993
|
-
return;
|
|
51164
|
+
return null;
|
|
50994
51165
|
}
|
|
50995
51166
|
const rootRect = rootEl.getBoundingClientRect();
|
|
50996
51167
|
let block = "nearest";
|
|
@@ -51002,12 +51173,74 @@ const scrollExpandableIntoView = (rootEl, {
|
|
|
51002
51173
|
} else if (rootRect.width > scrollerEl.clientWidth) {
|
|
51003
51174
|
inline = revealDirection === "right" ? "start" : "end";
|
|
51004
51175
|
}
|
|
51005
|
-
|
|
51176
|
+
const {
|
|
51177
|
+
left,
|
|
51178
|
+
top
|
|
51179
|
+
} = getScrollIntoViewScopedOffsets(rootEl, {
|
|
51006
51180
|
container: scrollerEl,
|
|
51007
51181
|
block,
|
|
51008
|
-
inline
|
|
51009
|
-
behavior
|
|
51182
|
+
inline
|
|
51010
51183
|
});
|
|
51184
|
+
if (left === scrollerEl.scrollLeft && top === scrollerEl.scrollTop) {
|
|
51185
|
+
return null;
|
|
51186
|
+
}
|
|
51187
|
+
return {
|
|
51188
|
+
scrollerEl,
|
|
51189
|
+
left,
|
|
51190
|
+
top
|
|
51191
|
+
};
|
|
51192
|
+
};
|
|
51193
|
+
|
|
51194
|
+
// Moves the scroller toward where the reveal ends at the pace of the reveal
|
|
51195
|
+
// itself: every frame the track's progress is read from the container's size
|
|
51196
|
+
// and the scroll stands the same fraction of its own way. The scroll cannot
|
|
51197
|
+
// run ahead of the track: only the box's own growth extends the scroller's
|
|
51198
|
+
// range, and what the target adds around the box (a parent's padding, a
|
|
51199
|
+
// scroll-margin) becomes reachable as the track grows — a scroll started at
|
|
51200
|
+
// once would be clamped short of it. One clock, the transition's, so the
|
|
51201
|
+
// content never waits below the fold either. Returns what stops the ride.
|
|
51202
|
+
const rideScrollerAlongReveal = ({
|
|
51203
|
+
scrollerEl,
|
|
51204
|
+
left,
|
|
51205
|
+
top
|
|
51206
|
+
}, {
|
|
51207
|
+
contentContainer,
|
|
51208
|
+
axis,
|
|
51209
|
+
startSize,
|
|
51210
|
+
finalSize
|
|
51211
|
+
}) => {
|
|
51212
|
+
const startLeft = scrollerEl.scrollLeft;
|
|
51213
|
+
const startTop = scrollerEl.scrollTop;
|
|
51214
|
+
const leftDelta = left - startLeft;
|
|
51215
|
+
const topDelta = top - startTop;
|
|
51216
|
+
let frameId;
|
|
51217
|
+
const tick = () => {
|
|
51218
|
+
const rect = contentContainer.getBoundingClientRect();
|
|
51219
|
+
const size = axis === "x" ? rect.width : rect.height;
|
|
51220
|
+
let progress = 1;
|
|
51221
|
+
if (finalSize !== startSize) {
|
|
51222
|
+
progress = (size - startSize) / (finalSize - startSize);
|
|
51223
|
+
if (progress < 0) {
|
|
51224
|
+
progress = 0;
|
|
51225
|
+
} else if (progress > 1) {
|
|
51226
|
+
progress = 1;
|
|
51227
|
+
}
|
|
51228
|
+
}
|
|
51229
|
+
scrollerEl.scrollTo({
|
|
51230
|
+
left: startLeft + leftDelta * progress,
|
|
51231
|
+
top: startTop + topDelta * progress,
|
|
51232
|
+
// Driven frame by frame: a smooth scroll-behavior on the scroller is
|
|
51233
|
+
// for a scroll that lands, not for one that is held.
|
|
51234
|
+
behavior: "instant"
|
|
51235
|
+
});
|
|
51236
|
+
if (progress < 1) {
|
|
51237
|
+
frameId = requestAnimationFrame(tick);
|
|
51238
|
+
}
|
|
51239
|
+
};
|
|
51240
|
+
frameId = requestAnimationFrame(tick);
|
|
51241
|
+
return () => {
|
|
51242
|
+
cancelAnimationFrame(frameId);
|
|
51243
|
+
};
|
|
51011
51244
|
};
|
|
51012
51245
|
const createToggleEvent = open => {
|
|
51013
51246
|
const newState = open ? "open" : "closed";
|
|
@@ -74139,11 +74372,15 @@ const PickerNaviTime = props => {
|
|
|
74139
74372
|
max = "23:30",
|
|
74140
74373
|
step
|
|
74141
74374
|
} = props;
|
|
74142
|
-
|
|
74375
|
+
// resolveInputProps has already run (PickerFirstResolver) and left navi_time
|
|
74376
|
+
// as written: what is handed on is a time input, whose step is in seconds.
|
|
74377
|
+
const hostStep = timeStringToSeconds(step);
|
|
74378
|
+
const stepSeconds = hostStep ?? 1800;
|
|
74143
74379
|
const slots = useMemo(() => generateTimeSlots(min, max, stepSeconds), [min, max, stepSeconds]);
|
|
74144
74380
|
return jsx(Next, {
|
|
74145
74381
|
...props,
|
|
74146
74382
|
type: "time",
|
|
74383
|
+
step: hostStep,
|
|
74147
74384
|
children: jsx(List, {
|
|
74148
74385
|
selectable: true,
|
|
74149
74386
|
command: "--navi-send",
|
|
@@ -77189,6 +77426,10 @@ const warnOnUnknownPickerType = props => {
|
|
|
77189
77426
|
pickerTypeWarnedSet.add(type);
|
|
77190
77427
|
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.`);
|
|
77191
77428
|
};
|
|
77429
|
+
|
|
77430
|
+
// PickerFirstResolver comes before the presets so a preset reads the
|
|
77431
|
+
// min/max/step a bound signal fills in (resolveInputProps); a preset type
|
|
77432
|
+
// therefore has no row in NAVI_TYPE_DEFAULTS (resolve_input_props.js).
|
|
77192
77433
|
const PickerResolved = /*#__PURE__*/createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerConfirmResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
77193
77434
|
const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
77194
77435
|
Date: PickerDateUI,
|