@jsenv/navi 0.29.458 → 0.29.460
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 +343 -99
- package/dist/dev/jsenv_navi.js.map +19 -20
- package/dist/jsenv_navi.js +343 -99
- package/dist/jsenv_navi.js.map +19 -20
- package/docs/actions.md +15 -0
- package/docs/mobile_touch.md +56 -0
- package/docs/navigation.md +4 -2
- package/docs/popup_open.md +17 -0
- package/docs/resource.md +21 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -1895,6 +1895,27 @@ const markErrorAsTakenByRender = (error) => {
|
|
|
1895
1895
|
}
|
|
1896
1896
|
};
|
|
1897
1897
|
|
|
1898
|
+
/**
|
|
1899
|
+
* A prerun's failure is owed to no screen: nothing on screen asked for that
|
|
1900
|
+
* run, so the component that owns the read asks again rather than show a
|
|
1901
|
+
* failure from before it existed (see `run` in use_async_data.jsx). Taken
|
|
1902
|
+
* once, so the run asking again fails as its own even when it fails with the
|
|
1903
|
+
* same error object. Held by the error, it lasts as long as the failure stays
|
|
1904
|
+
* on the action: a later run replaces it, whether or not an owner ever came.
|
|
1905
|
+
*/
|
|
1906
|
+
const prerunFailureSet = new WeakSet();
|
|
1907
|
+
const markErrorAsPrerunFailure = (error) => {
|
|
1908
|
+
if (error && typeof error === "object") {
|
|
1909
|
+
prerunFailureSet.add(error);
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
const takePrerunFailure = (error) => {
|
|
1913
|
+
if (!error || typeof error !== "object") {
|
|
1914
|
+
return false;
|
|
1915
|
+
}
|
|
1916
|
+
return prerunFailureSet.delete(error);
|
|
1917
|
+
};
|
|
1918
|
+
|
|
1898
1919
|
/**
|
|
1899
1920
|
* When the answer "nobody took it" is final.
|
|
1900
1921
|
*
|
|
@@ -4194,6 +4215,9 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4194
4215
|
return error;
|
|
4195
4216
|
}
|
|
4196
4217
|
error.action = action;
|
|
4218
|
+
if (isPrerun) {
|
|
4219
|
+
markErrorAsPrerunFailure(error);
|
|
4220
|
+
}
|
|
4197
4221
|
batch(() => {
|
|
4198
4222
|
errorSignal.value = error;
|
|
4199
4223
|
runningStateSignal.value = FAILED;
|
|
@@ -4767,6 +4791,25 @@ const runUnwatched = (startRun) => {
|
|
|
4767
4791
|
return result;
|
|
4768
4792
|
};
|
|
4769
4793
|
|
|
4794
|
+
/**
|
|
4795
|
+
* Starts a prerun navi has nobody to await. Its failure is not reported:
|
|
4796
|
+
* nothing on screen asked for it. It stays on the action, marked as a
|
|
4797
|
+
* prerun's (see markErrorAsPrerunFailure), where whatever owns the read asks
|
|
4798
|
+
* again.
|
|
4799
|
+
*/
|
|
4800
|
+
const prerunUnwatched = (startPrerun) => {
|
|
4801
|
+
let result;
|
|
4802
|
+
try {
|
|
4803
|
+
result = startPrerun();
|
|
4804
|
+
} catch {
|
|
4805
|
+
return undefined;
|
|
4806
|
+
}
|
|
4807
|
+
if (result && typeof result.catch === "function") {
|
|
4808
|
+
result.catch(() => {});
|
|
4809
|
+
}
|
|
4810
|
+
return result;
|
|
4811
|
+
};
|
|
4812
|
+
|
|
4770
4813
|
/**
|
|
4771
4814
|
* Reactively runs an action whenever the params derived from signals change.
|
|
4772
4815
|
*
|
|
@@ -10882,27 +10925,29 @@ const updateDocumentState = (value) => {
|
|
|
10882
10925
|
* `state`:
|
|
10883
10926
|
* - `undefined` — a neutral navigation: a link click, a `replaceUrl` writing a
|
|
10884
10927
|
* search param. A **replace** stays on the entry the document is already on,
|
|
10885
|
-
* so what that entry holds stays with it. A **push** opens a NEW entry
|
|
10886
|
-
*
|
|
10887
|
-
*
|
|
10888
|
-
*
|
|
10889
|
-
*
|
|
10928
|
+
* so what that entry holds stays with it. A **push** opens a NEW entry. One
|
|
10929
|
+
* arriving at another page (`isArrival`, see scroll_restoration.js) starts
|
|
10930
|
+
* empty: what was written for the entry being left — an open dialog, an
|
|
10931
|
+
* expanded picker (see `useNavState`) — describes that entry alone, and
|
|
10932
|
+
* carried forward it would reopen on the next screen, and on the one after
|
|
10933
|
+
* that, until something mounting the same id opens out of nowhere. A push
|
|
10934
|
+
* that keeps the page (a layer bound to a search param, opened over it)
|
|
10935
|
+
* carries the state: whatever holds it is still mounted under the new
|
|
10936
|
+
* address, and an empty entry would close it there — an expanded row
|
|
10937
|
+
* collapsing under the layer, expanding again when the back takes it away.
|
|
10890
10938
|
* - `null` — an explicit reset.
|
|
10891
10939
|
* - an object — built by the caller (`enter()`/`leave()` copy the current state
|
|
10892
10940
|
* themselves), taken as given.
|
|
10893
10941
|
*
|
|
10894
|
-
* `
|
|
10895
|
-
*
|
|
10896
|
-
* The push/replace split is the Navigation API's own rule for a `navigate()`
|
|
10897
|
-
* carrying no state — which is why via_navigation.js gets it from the browser
|
|
10898
|
-
* and via_history.js has to spell it out to say the same thing.
|
|
10942
|
+
* `currentState` is what the entry being left holds. `sharedState` always
|
|
10943
|
+
* wins: it describes the document, not the entry.
|
|
10899
10944
|
*/
|
|
10900
10945
|
const resolveEffectiveDocumentState = (
|
|
10901
10946
|
state,
|
|
10902
|
-
{ navigationType, currentState, sharedState },
|
|
10947
|
+
{ navigationType, isArrival, currentState, sharedState },
|
|
10903
10948
|
) => {
|
|
10904
10949
|
if (state === undefined) {
|
|
10905
|
-
if (navigationType === "push") {
|
|
10950
|
+
if (navigationType === "push" && isArrival) {
|
|
10906
10951
|
return sharedState;
|
|
10907
10952
|
}
|
|
10908
10953
|
return { ...(currentState || {}), ...sharedState };
|
|
@@ -11446,6 +11491,8 @@ const startAtTop = (url, { from } = {}) => {
|
|
|
11446
11491
|
positionByUrl.set(href, { x: 0, y: 0 });
|
|
11447
11492
|
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
11448
11493
|
};
|
|
11494
|
+
// Also what decides whether a push carries the state of the page it keeps
|
|
11495
|
+
// (see resolveEffectiveDocumentState).
|
|
11449
11496
|
const isArrival = (url, { from }) => {
|
|
11450
11497
|
if (
|
|
11451
11498
|
from !== undefined &&
|
|
@@ -11881,7 +11928,8 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11881
11928
|
|
|
11882
11929
|
const applyRoutingTask = (url, options) => {
|
|
11883
11930
|
// Read before the history is written: the url the reader is being taken
|
|
11884
|
-
// away from, which is what decides where a push lands (see startAtTop)
|
|
11931
|
+
// away from, which is what decides where a push lands (see startAtTop) and
|
|
11932
|
+
// whether it carries the state (see resolveEffectiveDocumentState).
|
|
11885
11933
|
const urlLeft = readAddress();
|
|
11886
11934
|
const {
|
|
11887
11935
|
reason,
|
|
@@ -11901,6 +11949,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11901
11949
|
markUrlAsVisited(url);
|
|
11902
11950
|
const effectiveState = resolveEffectiveDocumentState(state, {
|
|
11903
11951
|
navigationType,
|
|
11952
|
+
isArrival: isArrival(url, { from: urlLeft }),
|
|
11904
11953
|
currentState: getDocumentState(),
|
|
11905
11954
|
sharedState: {
|
|
11906
11955
|
jsenv_visited_urls: Array.from(visitedUrlSet),
|
|
@@ -25567,10 +25616,24 @@ const resolveRerunOn = (rerunOn, inheritedRerunOn = defaultRerunOn) => {
|
|
|
25567
25616
|
};
|
|
25568
25617
|
};
|
|
25569
25618
|
|
|
25619
|
+
// What a `dependencies` entry names are callbacks, not verbs (`rerunOn` names
|
|
25620
|
+
// verbs, where `PATCH` covers `PATCH_MANY`): one callback of a resource can be
|
|
25621
|
+
// the write that creates (a move into a new item) while its sibling only edits.
|
|
25622
|
+
const WRITE_CALLBACK_NAMES = [
|
|
25623
|
+
"POST",
|
|
25624
|
+
"POST_MANY",
|
|
25625
|
+
"PUT",
|
|
25626
|
+
"PUT_MANY",
|
|
25627
|
+
"PATCH",
|
|
25628
|
+
"PATCH_MANY",
|
|
25629
|
+
"DELETE",
|
|
25630
|
+
"DELETE_MANY",
|
|
25631
|
+
];
|
|
25632
|
+
|
|
25570
25633
|
// This handles ALL resource lifecycle logic (rerun/reset) across all resources
|
|
25571
25634
|
const createResourceLifecycleManager = () => {
|
|
25572
25635
|
const registeredResources = new Map(); // Map<resourceInstance, lifecycleConfig>
|
|
25573
|
-
const resourceDependencies = new Map(); // Map<resourceInstance, Set<
|
|
25636
|
+
const resourceDependencies = new Map(); // Map<resourceInstance, Map<dependentResource, Set<writeCallbackName>>> — user-configured
|
|
25574
25637
|
const scopedManyParents = new Map(); // Map<childResource, Set<{resource, propertyName}>> — auto from scopedMany
|
|
25575
25638
|
|
|
25576
25639
|
const registerResource = (resourceScope, config) => {
|
|
@@ -25589,16 +25652,38 @@ const createResourceLifecycleManager = () => {
|
|
|
25589
25652
|
rangeReaderSet: new Set(),
|
|
25590
25653
|
});
|
|
25591
25654
|
|
|
25592
|
-
|
|
25593
|
-
|
|
25594
|
-
|
|
25595
|
-
|
|
25596
|
-
|
|
25597
|
-
|
|
25598
|
-
|
|
25655
|
+
for (const dependency of dependencies) {
|
|
25656
|
+
const [dependencyScope, writeNames] = resolveDependency(
|
|
25657
|
+
dependency,
|
|
25658
|
+
resourceScope,
|
|
25659
|
+
);
|
|
25660
|
+
let dependentMap = resourceDependencies.get(dependencyScope);
|
|
25661
|
+
if (!dependentMap) {
|
|
25662
|
+
dependentMap = new Map();
|
|
25663
|
+
resourceDependencies.set(dependencyScope, dependentMap);
|
|
25664
|
+
}
|
|
25665
|
+
let writeNameSet = dependentMap.get(resourceScope);
|
|
25666
|
+
if (!writeNameSet) {
|
|
25667
|
+
writeNameSet = new Set();
|
|
25668
|
+
dependentMap.set(resourceScope, writeNameSet);
|
|
25669
|
+
}
|
|
25670
|
+
for (const writeName of writeNames) {
|
|
25671
|
+
writeNameSet.add(writeName);
|
|
25599
25672
|
}
|
|
25600
25673
|
}
|
|
25601
25674
|
};
|
|
25675
|
+
// Whether `resourceScope` listed the facade that wrote, with this write
|
|
25676
|
+
// among the ones that count.
|
|
25677
|
+
const isWriteHeardBy = (resourceScope, triggeringAction, triggerScope) => {
|
|
25678
|
+
const writeNameSet = resourceDependencies
|
|
25679
|
+
.get(triggerScope)
|
|
25680
|
+
?.get(resourceScope);
|
|
25681
|
+
if (!writeNameSet) {
|
|
25682
|
+
return false;
|
|
25683
|
+
}
|
|
25684
|
+
const { verb, isMany } = triggeringAction.meta;
|
|
25685
|
+
return writeNameSet.has(isMany ? `${verb}_MANY` : verb);
|
|
25686
|
+
};
|
|
25602
25687
|
// Only the reader the resource exposes is registered; the ones `bindParams`
|
|
25603
25688
|
// makes share its signal (see resource_range_reader.js).
|
|
25604
25689
|
const registerRangeReader = (resourceScope, rangeReader) => {
|
|
@@ -25639,17 +25724,18 @@ const createResourceLifecycleManager = () => {
|
|
|
25639
25724
|
triggerVerb === "PATCH") &&
|
|
25640
25725
|
config.uniqueKeys.length > 0;
|
|
25641
25726
|
|
|
25642
|
-
const
|
|
25643
|
-
|
|
25644
|
-
|
|
25645
|
-
|
|
25727
|
+
const isHeardDependency = isWriteHeardBy(
|
|
25728
|
+
resourceScope,
|
|
25729
|
+
triggeringAction,
|
|
25730
|
+
triggerResourceScope,
|
|
25731
|
+
);
|
|
25646
25732
|
|
|
25647
25733
|
if (
|
|
25648
25734
|
!shouldRerunGetMany &&
|
|
25649
25735
|
!shouldRerunGet &&
|
|
25650
25736
|
triggerVerb !== "DELETE" &&
|
|
25651
25737
|
!hasUniqueKeyAutorerun &&
|
|
25652
|
-
!
|
|
25738
|
+
!isHeardDependency
|
|
25653
25739
|
) {
|
|
25654
25740
|
continue;
|
|
25655
25741
|
}
|
|
@@ -25770,21 +25856,10 @@ const createResourceLifecycleManager = () => {
|
|
|
25770
25856
|
}
|
|
25771
25857
|
|
|
25772
25858
|
// Cross-resource dependency effects: rerun dependent GET / GET_MANY
|
|
25773
|
-
//
|
|
25774
|
-
//
|
|
25775
|
-
// (POST, PUT, PATCH, DELETE) on the dependency should trigger a rerun.
|
|
25859
|
+
// after a write the dependency counts — every write for a resource
|
|
25860
|
+
// listed bare, the callbacks named in `on` otherwise.
|
|
25776
25861
|
{
|
|
25777
|
-
if (
|
|
25778
|
-
triggerResourceScope &&
|
|
25779
|
-
resourceDependencies
|
|
25780
|
-
.get(triggerResourceScope)
|
|
25781
|
-
?.has(resourceScope) &&
|
|
25782
|
-
(triggerVerb === "POST" ||
|
|
25783
|
-
triggerVerb === "PUT" ||
|
|
25784
|
-
triggerVerb === "PATCH" ||
|
|
25785
|
-
triggerVerb === "DELETE") &&
|
|
25786
|
-
candidateVerb === "GET"
|
|
25787
|
-
) {
|
|
25862
|
+
if (isHeardDependency && candidateVerb === "GET") {
|
|
25788
25863
|
actionsToRerun.add(actionCandidate);
|
|
25789
25864
|
reasonSet.add("dependency autorerun");
|
|
25790
25865
|
continue;
|
|
@@ -25861,9 +25936,10 @@ const createResourceLifecycleManager = () => {
|
|
|
25861
25936
|
continue;
|
|
25862
25937
|
}
|
|
25863
25938
|
const isSameResource = resourceScope === triggerResourceScope;
|
|
25864
|
-
const isDependent =
|
|
25865
|
-
|
|
25866
|
-
|
|
25939
|
+
const isDependent = isWriteHeardBy(
|
|
25940
|
+
resourceScope,
|
|
25941
|
+
triggeringAction,
|
|
25942
|
+
triggerResourceScope,
|
|
25867
25943
|
);
|
|
25868
25944
|
if (!isSameResource && !isDependent) {
|
|
25869
25945
|
continue;
|
|
@@ -25928,6 +26004,35 @@ const shouldRerunAfter = (rerunConfig, verb) => {
|
|
|
25928
26004
|
}
|
|
25929
26005
|
return false;
|
|
25930
26006
|
};
|
|
26007
|
+
// A `dependencies` entry: a resource (every write counts), or
|
|
26008
|
+
// `{ resource, on }` naming the write callbacks that do. A name that cannot
|
|
26009
|
+
// fire throws here: a dependency that never fires leaves a read on its old
|
|
26010
|
+
// value with nothing to say so.
|
|
26011
|
+
const resolveDependency = (dependency, dependentScope) => {
|
|
26012
|
+
if (!Object.hasOwn(dependency, "resource")) {
|
|
26013
|
+
return [dependency, WRITE_CALLBACK_NAMES];
|
|
26014
|
+
}
|
|
26015
|
+
const { resource: dependencyScope, on } = dependency;
|
|
26016
|
+
const where = `resource(${dependentScope.name}) dependency on ${dependencyScope.name}`;
|
|
26017
|
+
if (!Array.isArray(on) || on.length === 0) {
|
|
26018
|
+
throw new Error(
|
|
26019
|
+
`${where}: "on" lists the writes that count, e.g. on: ["POST", "DELETE"] — list the resource itself for every write`,
|
|
26020
|
+
);
|
|
26021
|
+
}
|
|
26022
|
+
for (const writeName of on) {
|
|
26023
|
+
if (!WRITE_CALLBACK_NAMES.includes(writeName)) {
|
|
26024
|
+
throw new Error(
|
|
26025
|
+
`${where}: "${writeName}" is not a write, "on" takes ${WRITE_CALLBACK_NAMES.join(", ")}`,
|
|
26026
|
+
);
|
|
26027
|
+
}
|
|
26028
|
+
if (!dependencyScope[writeName]) {
|
|
26029
|
+
throw new Error(
|
|
26030
|
+
`${where}: ${dependencyScope.name} has no ${writeName}, so it would never rerun ${dependentScope.name}`,
|
|
26031
|
+
);
|
|
26032
|
+
}
|
|
26033
|
+
}
|
|
26034
|
+
return [dependencyScope, on];
|
|
26035
|
+
};
|
|
25931
26036
|
const isParamSubset = (parentParams, childParams) => {
|
|
25932
26037
|
if (!parentParams || !childParams) {
|
|
25933
26038
|
return false;
|
|
@@ -26676,6 +26781,10 @@ const debug$2 = (...args) => {
|
|
|
26676
26781
|
* @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY, GET_RANGE, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
26677
26782
|
* @param {string} [restCallbacks.idKey] - primary key property, defaults to `"id"`
|
|
26678
26783
|
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`)
|
|
26784
|
+
* @param {Array<Object | { resource: Object, on: string[] }>} [restCallbacks.dependencies] -
|
|
26785
|
+
* resources whose writes rerun this one's reads: one listed bare counts every
|
|
26786
|
+
* write, `{ resource, on: ["PATCH_MANY", "DELETE"] }` only the callbacks it
|
|
26787
|
+
* names (`PATCH` is not `PATCH_MANY`)
|
|
26679
26788
|
* @param {{ signal: import("@preact/signals").Signal, when?: () => boolean }} [restCallbacks.persist] -
|
|
26680
26789
|
* mirrors the rows `GET` lands with into `signal` — a `stateSignal` with
|
|
26681
26790
|
* `persists: true` and `type: "object"` keeps them across a reload, whose
|
|
@@ -28766,6 +28875,7 @@ const useAsyncData = (promiseOrAction, {
|
|
|
28766
28875
|
loading = "delegate",
|
|
28767
28876
|
error = "delegate",
|
|
28768
28877
|
run,
|
|
28878
|
+
prerun,
|
|
28769
28879
|
onLoad
|
|
28770
28880
|
} = {}) => {
|
|
28771
28881
|
// The request this component owns, as a function: one action per instance,
|
|
@@ -28791,6 +28901,7 @@ const useAsyncData = (promiseOrAction, {
|
|
|
28791
28901
|
loadingEffect: loading,
|
|
28792
28902
|
errorEffect: error,
|
|
28793
28903
|
run,
|
|
28904
|
+
prerun,
|
|
28794
28905
|
onLoad
|
|
28795
28906
|
});
|
|
28796
28907
|
}
|
|
@@ -28822,8 +28933,14 @@ const useActionAsyncData = (action, {
|
|
|
28822
28933
|
loadingEffect,
|
|
28823
28934
|
errorEffect,
|
|
28824
28935
|
run,
|
|
28936
|
+
prerun,
|
|
28825
28937
|
onLoad
|
|
28826
28938
|
}) => {
|
|
28939
|
+
if (prerun) {
|
|
28940
|
+
// A head start draws none of what it starts, so it has no wait to
|
|
28941
|
+
// delegate.
|
|
28942
|
+
loadingEffect = "use";
|
|
28943
|
+
}
|
|
28827
28944
|
const loadingRef = useContext(LoadingContext);
|
|
28828
28945
|
// The boundary is where a suspension lands, so only the delegating path needs
|
|
28829
28946
|
// one: `loading: true` draws the wait itself and never suspends.
|
|
@@ -28839,13 +28956,18 @@ const useActionAsyncData = (action, {
|
|
|
28839
28956
|
// no-op — which is what makes starting it while rendering sound. From there
|
|
28840
28957
|
// the wait is the ordinary one: suspended into <Loading>, or drawn by the
|
|
28841
28958
|
// component under `loading: true`, exactly as for data someone else ran.
|
|
28842
|
-
if (run && action.
|
|
28959
|
+
if (run && action.paramsSignal.peek() !== undefined && runIsOwed(action)) {
|
|
28843
28960
|
// Nothing waits on this run: the failure is held by the action, and this
|
|
28844
28961
|
// hook is what reads it back.
|
|
28845
28962
|
runUnwatched(() => action.run({
|
|
28846
28963
|
reason: "useAsyncData({ run: true })"
|
|
28847
28964
|
}));
|
|
28848
28965
|
}
|
|
28966
|
+
if (prerun && action.runningStateSignal.peek() === IDLE && action.paramsSignal.peek() !== undefined) {
|
|
28967
|
+
prerunUnwatched(() => action.prerun({
|
|
28968
|
+
reason: "useAsyncData({ prerun: true })"
|
|
28969
|
+
}));
|
|
28970
|
+
}
|
|
28849
28971
|
|
|
28850
28972
|
// Use peek() instead of .value to avoid subscribing this component to the signal.
|
|
28851
28973
|
// Reading .value would make Preact re-render the component reactively when the state
|
|
@@ -28954,6 +29076,11 @@ const useActionAsyncData = (action, {
|
|
|
28954
29076
|
return [action.dataSignal.peek(), false, undefined];
|
|
28955
29077
|
}
|
|
28956
29078
|
if (runningState === FAILED) {
|
|
29079
|
+
if (prerun) {
|
|
29080
|
+
// Neither shown nor reported from a head start: the failure is the
|
|
29081
|
+
// owner's to ask again (see runIsOwed).
|
|
29082
|
+
return [action.dataSignal.peek(), false, undefined];
|
|
29083
|
+
}
|
|
28957
29084
|
if (dismissedActionWeakSet.has(action)) {
|
|
28958
29085
|
// Dismissing removes the error and nothing else: what is handed back is
|
|
28959
29086
|
// the action's state without it — the data it holds, and no run in
|
|
@@ -29059,6 +29186,21 @@ const useActionAsyncData = (action, {
|
|
|
29059
29186
|
throw pendingPromise;
|
|
29060
29187
|
};
|
|
29061
29188
|
|
|
29189
|
+
// What `run: true` starts from: an action nobody ran, or one a prerun failed.
|
|
29190
|
+
// Any other failure stays — a render retrying it would loop while the network
|
|
29191
|
+
// is down — but nothing on screen asked for a prerun, so its failure is asked
|
|
29192
|
+
// again, once: the mark is taken here, and the run started fails as its own.
|
|
29193
|
+
const runIsOwed = action => {
|
|
29194
|
+
const runningState = action.runningStateSignal.peek();
|
|
29195
|
+
if (runningState === IDLE) {
|
|
29196
|
+
return true;
|
|
29197
|
+
}
|
|
29198
|
+
if (runningState === FAILED) {
|
|
29199
|
+
return takePrerunFailure(action.errorSignal.peek());
|
|
29200
|
+
}
|
|
29201
|
+
return false;
|
|
29202
|
+
};
|
|
29203
|
+
|
|
29062
29204
|
// What a screen does with the data once, when it becomes known (see onLoad in
|
|
29063
29205
|
// the JSDoc above). Kept apart because the two questions it answers are not the
|
|
29064
29206
|
// ones the hook around it answers: WHEN — a layout effect, so a form taking its
|
|
@@ -38595,15 +38737,7 @@ const prefetchAsIf = (action, readParams, values) => {
|
|
|
38595
38737
|
// the arrival focuses the link, or is the one that asked), and a reset
|
|
38596
38738
|
// landing after that would pull the answer from under the page reading it.
|
|
38597
38739
|
const prerunAhead = (instance) => {
|
|
38598
|
-
|
|
38599
|
-
try {
|
|
38600
|
-
result = instance.prerun({ reason: "intent" });
|
|
38601
|
-
} catch {
|
|
38602
|
-
return;
|
|
38603
|
-
}
|
|
38604
|
-
if (result && typeof result.catch === "function") {
|
|
38605
|
-
result.catch(() => {});
|
|
38606
|
-
}
|
|
38740
|
+
prerunUnwatched(() => instance.prerun({ reason: "intent" }));
|
|
38607
38741
|
};
|
|
38608
38742
|
|
|
38609
38743
|
// I delibrately prefer the term "any" and avoid "some" so dev are not tempted to think
|
|
@@ -40640,7 +40774,7 @@ registerNaviCommand("--navi-open", (source, event, { anchor, value } = {}) => {
|
|
|
40640
40774
|
* The target is found the way --navi-open finds it, but silently: nothing
|
|
40641
40775
|
* runs here, and a source with no target says so at the release.
|
|
40642
40776
|
*/
|
|
40643
|
-
const announceOpeningPress = (source, command) => {
|
|
40777
|
+
const announceOpeningPress = (source, command, pointerdownEvent) => {
|
|
40644
40778
|
if (command !== "--navi-open" && command !== "--navi-toggle") {
|
|
40645
40779
|
return;
|
|
40646
40780
|
}
|
|
@@ -40651,7 +40785,7 @@ const announceOpeningPress = (source, command) => {
|
|
|
40651
40785
|
if (!target) {
|
|
40652
40786
|
return;
|
|
40653
40787
|
}
|
|
40654
|
-
dispatchCustomEvent(target, "navi_open_press");
|
|
40788
|
+
dispatchCustomEvent(target, "navi_open_press", { event: pointerdownEvent });
|
|
40655
40789
|
};
|
|
40656
40790
|
// "--navi-close:all" closes every expandable above the source, nearest first —
|
|
40657
40791
|
// a link leaving from a badge shown over a sheet leaves both. A surface that
|
|
@@ -40924,11 +41058,18 @@ registerNaviCommand("--navi-unselect", (source, event) => {
|
|
|
40924
41058
|
* where a delay is felt hardest — while the ~100-300ms between hovering a
|
|
40925
41059
|
* trigger and pressing it are free. The warming render starts at the frame
|
|
40926
41060
|
* after the one that follows the intent: nothing here needs the content in the
|
|
40927
|
-
* DOM before the click, only before the open that follows it, and
|
|
40928
|
-
*
|
|
40929
|
-
*
|
|
40930
|
-
*
|
|
40931
|
-
*
|
|
41061
|
+
* DOM before the click, only before the open that follows it, and a build
|
|
41062
|
+
* started at once would hold back the frame that shows the hover or the focus.
|
|
41063
|
+
*
|
|
41064
|
+
* Under a finger, `pointerover` is not an intent yet: it arrives with the
|
|
41065
|
+
* `pointerdown` of every touch, the ones starting a scroll included, and at
|
|
41066
|
+
* reading speed the browser decides a touch is a scroll about 35ms in — a
|
|
41067
|
+
* build started then holds that scroll start back. So a touch warms once it has been held
|
|
41068
|
+
* TOUCH_WARM_HOLD_MS without the browser taking it (`pointercancel`) or the
|
|
41069
|
+
* finger lifting; a quicker tap builds at the open, like a click with no hover
|
|
41070
|
+
* before it. Counted in animation frames, never a timeout: browsers hold timers
|
|
41071
|
+
* back while a finger is down, and a timeout started by the press fires at its
|
|
41072
|
+
* release, on top of the click.
|
|
40932
41073
|
*
|
|
40933
41074
|
* The anchor only says where the popup is placed, so intent inside it is read
|
|
40934
41075
|
* off the control it is aimed at, the nearest one around the target. A control
|
|
@@ -40952,8 +41093,11 @@ registerNaviCommand("--navi-unselect", (source, event) => {
|
|
|
40952
41093
|
* anchor: it says where the popup is placed, not what opens it — a card
|
|
40953
41094
|
* opened by a hold is pressed all day by taps meant for what it holds.
|
|
40954
41095
|
*
|
|
40955
|
-
* The press builds the content,
|
|
40956
|
-
*
|
|
41096
|
+
* The press builds the content when it starts, a touch once it has been held
|
|
41097
|
+
* as above: the announcement carries the pointerdown that made it. A press
|
|
41098
|
+
* released before its build leaves the building to the open its click asks
|
|
41099
|
+
* for. The press ending without an open throws the content away — its click
|
|
41100
|
+
* reaching the document with the popup still closed, no
|
|
40957
41101
|
* click coming after the release, the browser taking the gesture
|
|
40958
41102
|
* (`pointercancel`), or the next press starting, before any of its own
|
|
40959
41103
|
* handlers run: one press at a time holds a content. A keyboard opening builds
|
|
@@ -40997,6 +41141,12 @@ const cancelIdle = (id) =>
|
|
|
40997
41141
|
? cancelIdleCallback(id)
|
|
40998
41142
|
: clearTimeout(id);
|
|
40999
41143
|
|
|
41144
|
+
// How long a finger rests on an anchor before it warms it: past the ~35ms at
|
|
41145
|
+
// which the browser takes a touch moving at reading speed for a scroll, short
|
|
41146
|
+
// of the ~100ms a tap is held, so a deliberate tap is warmed before its
|
|
41147
|
+
// release.
|
|
41148
|
+
const TOUCH_WARM_HOLD_MS = 80;
|
|
41149
|
+
|
|
41000
41150
|
const usePopupContentMount = (
|
|
41001
41151
|
openController,
|
|
41002
41152
|
ref,
|
|
@@ -41087,14 +41237,35 @@ const usePopupContentMount = (
|
|
|
41087
41237
|
return undefined;
|
|
41088
41238
|
}
|
|
41089
41239
|
let cancelWarm = null;
|
|
41240
|
+
let warmIsTouchHold = false;
|
|
41090
41241
|
const warm = () => {
|
|
41091
41242
|
if (cancelWarm) {
|
|
41092
41243
|
return;
|
|
41093
41244
|
}
|
|
41245
|
+
warmIsTouchHold = false;
|
|
41094
41246
|
cancelWarm = requestFrameAfterNext(() => {
|
|
41095
41247
|
setContentMounted(true);
|
|
41096
41248
|
});
|
|
41097
41249
|
};
|
|
41250
|
+
const warmAfterTouchHold = () => {
|
|
41251
|
+
if (cancelWarm) {
|
|
41252
|
+
return;
|
|
41253
|
+
}
|
|
41254
|
+
warmIsTouchHold = true;
|
|
41255
|
+
cancelWarm = requestFrameAfterDuration(TOUCH_WARM_HOLD_MS, () => {
|
|
41256
|
+
setContentMounted(true);
|
|
41257
|
+
});
|
|
41258
|
+
};
|
|
41259
|
+
// A touch leaves the anchor when the finger lifts or the browser takes the
|
|
41260
|
+
// touch: either way, before the hold said anything.
|
|
41261
|
+
const onPointerLeave = () => {
|
|
41262
|
+
if (!warmIsTouchHold) {
|
|
41263
|
+
return;
|
|
41264
|
+
}
|
|
41265
|
+
cancelWarm();
|
|
41266
|
+
cancelWarm = null;
|
|
41267
|
+
warmIsTouchHold = false;
|
|
41268
|
+
};
|
|
41098
41269
|
// The anchor itself when it is a control (a Popover anchored on the button
|
|
41099
41270
|
// opening it), or the one the popup is written in (a picker's trigger).
|
|
41100
41271
|
const isOwnControl = (control) =>
|
|
@@ -41114,6 +41285,10 @@ const usePopupContentMount = (
|
|
|
41114
41285
|
return;
|
|
41115
41286
|
}
|
|
41116
41287
|
}
|
|
41288
|
+
if (pointeroverEvent.pointerType === "touch") {
|
|
41289
|
+
warmAfterTouchHold();
|
|
41290
|
+
return;
|
|
41291
|
+
}
|
|
41117
41292
|
warm();
|
|
41118
41293
|
};
|
|
41119
41294
|
const onFocusIn = (focusinEvent) => {
|
|
@@ -41124,10 +41299,12 @@ const usePopupContentMount = (
|
|
|
41124
41299
|
warm();
|
|
41125
41300
|
};
|
|
41126
41301
|
anchorElement.addEventListener("pointerover", onPointerOver);
|
|
41302
|
+
anchorElement.addEventListener("pointerleave", onPointerLeave);
|
|
41127
41303
|
anchorElement.addEventListener("focusin", onFocusIn);
|
|
41128
41304
|
return () => {
|
|
41129
41305
|
cancelWarm?.();
|
|
41130
41306
|
anchorElement.removeEventListener("pointerover", onPointerOver);
|
|
41307
|
+
anchorElement.removeEventListener("pointerleave", onPointerLeave);
|
|
41131
41308
|
anchorElement.removeEventListener("focusin", onFocusIn);
|
|
41132
41309
|
};
|
|
41133
41310
|
}, [contentMounted, anchor, mount]);
|
|
@@ -41137,12 +41314,14 @@ const usePopupContentMount = (
|
|
|
41137
41314
|
const stopPressWarmRef = useRef(null);
|
|
41138
41315
|
openController.onOpeningPress =
|
|
41139
41316
|
mount === "while-opened"
|
|
41140
|
-
? () => {
|
|
41317
|
+
? (openPressEvent) => {
|
|
41141
41318
|
if (contentMountedRef.current || openController.onOpen) {
|
|
41142
41319
|
return;
|
|
41143
41320
|
}
|
|
41144
|
-
const cancelBuild =
|
|
41145
|
-
|
|
41321
|
+
const cancelBuild = requestBuildOnPress(openPressEvent, {
|
|
41322
|
+
onBuild: () => {
|
|
41323
|
+
setContentMounted(true);
|
|
41324
|
+
},
|
|
41146
41325
|
});
|
|
41147
41326
|
const stopWatching = watchPressEnd(() => {
|
|
41148
41327
|
stopPressWarmRef.current = null;
|
|
@@ -41204,6 +41383,64 @@ const requestFrameAfterNext = (callback) => {
|
|
|
41204
41383
|
};
|
|
41205
41384
|
};
|
|
41206
41385
|
|
|
41386
|
+
// What an opening press builds, it builds at the frame after the next one, so
|
|
41387
|
+
// the pressed trigger is painted first — under a finger, once the touch has
|
|
41388
|
+
// been held TOUCH_WARM_HOLD_MS (see the top comment). Released or taken by the
|
|
41389
|
+
// browser before then, the press builds nothing: the open its click asks for
|
|
41390
|
+
// builds, when one comes. Called from the press's own pointerdown, through its
|
|
41391
|
+
// navi_open_press: the capture listeners below only hear what follows.
|
|
41392
|
+
const requestBuildOnPress = (openPressEvent, { onBuild, onGiveUp }) => {
|
|
41393
|
+
const pointerdownEvent = openPressEvent.detail.event;
|
|
41394
|
+
const cancelFrame =
|
|
41395
|
+
pointerdownEvent.pointerType === "touch"
|
|
41396
|
+
? requestFrameAfterDuration(TOUCH_WARM_HOLD_MS, () => {
|
|
41397
|
+
stopListening();
|
|
41398
|
+
onBuild();
|
|
41399
|
+
})
|
|
41400
|
+
: requestFrameAfterNext(() => {
|
|
41401
|
+
stopListening();
|
|
41402
|
+
onBuild();
|
|
41403
|
+
});
|
|
41404
|
+
const onPressEnd = (pointerEvent) => {
|
|
41405
|
+
if (pointerEvent.pointerId !== pointerdownEvent.pointerId) {
|
|
41406
|
+
return;
|
|
41407
|
+
}
|
|
41408
|
+
cancel();
|
|
41409
|
+
onGiveUp?.();
|
|
41410
|
+
};
|
|
41411
|
+
const stopListening = () => {
|
|
41412
|
+
document.removeEventListener("pointerup", onPressEnd, { capture: true });
|
|
41413
|
+
document.removeEventListener("pointercancel", onPressEnd, {
|
|
41414
|
+
capture: true,
|
|
41415
|
+
});
|
|
41416
|
+
};
|
|
41417
|
+
const cancel = () => {
|
|
41418
|
+
cancelFrame();
|
|
41419
|
+
stopListening();
|
|
41420
|
+
};
|
|
41421
|
+
document.addEventListener("pointerup", onPressEnd, { capture: true });
|
|
41422
|
+
document.addEventListener("pointercancel", onPressEnd, { capture: true });
|
|
41423
|
+
return cancel;
|
|
41424
|
+
};
|
|
41425
|
+
|
|
41426
|
+
// The first frame at least `ms` after the call, read off the frames' own
|
|
41427
|
+
// timestamps: a 120Hz screen waits as long as a 60Hz one.
|
|
41428
|
+
const requestFrameAfterDuration = (ms, callback) => {
|
|
41429
|
+
const start = performance.now();
|
|
41430
|
+
let frame;
|
|
41431
|
+
const onFrame = (timestamp) => {
|
|
41432
|
+
if (timestamp - start >= ms) {
|
|
41433
|
+
callback();
|
|
41434
|
+
return;
|
|
41435
|
+
}
|
|
41436
|
+
frame = requestAnimationFrame(onFrame);
|
|
41437
|
+
};
|
|
41438
|
+
frame = requestAnimationFrame(onFrame);
|
|
41439
|
+
return () => {
|
|
41440
|
+
cancelAnimationFrame(frame);
|
|
41441
|
+
};
|
|
41442
|
+
};
|
|
41443
|
+
|
|
41207
41444
|
// How long the click of a released press may take to arrive. Nothing tells a
|
|
41208
41445
|
// page that a release brings no click — a press that became a drag has its
|
|
41209
41446
|
// click swallowed (suppressClickAfterGesture in @jsenv/dom) — so past this the
|
|
@@ -50245,7 +50482,7 @@ const usePreloadOpeningOnPress = (ref, command, prefetch = true) => {
|
|
|
50245
50482
|
if (pointerdownEvent.button !== 0) {
|
|
50246
50483
|
return;
|
|
50247
50484
|
}
|
|
50248
|
-
announceOpeningPress(element, command);
|
|
50485
|
+
announceOpeningPress(element, command, pointerdownEvent);
|
|
50249
50486
|
};
|
|
50250
50487
|
element.addEventListener("pointerdown", onPointerDown);
|
|
50251
50488
|
return () => {
|
|
@@ -54421,7 +54658,9 @@ const Expandable = props => {
|
|
|
54421
54658
|
// its own (see announceOpeningPress in commands.js) — unless aimed at a
|
|
54422
54659
|
// control inside the UI part, whose click does not toggle (see onUIClick).
|
|
54423
54660
|
if (pointerdownEvent.button === 0 && !openController.opened && !isAimedAtControlInsideUI(pointerdownEvent.target)) {
|
|
54424
|
-
dispatchCustomEvent(rootRef.current, "navi_open_press"
|
|
54661
|
+
dispatchCustomEvent(rootRef.current, "navi_open_press", {
|
|
54662
|
+
event: pointerdownEvent
|
|
54663
|
+
});
|
|
54425
54664
|
}
|
|
54426
54665
|
};
|
|
54427
54666
|
|
|
@@ -54842,7 +55081,7 @@ const Expandable = props => {
|
|
|
54842
55081
|
,
|
|
54843
55082
|
onnavi_open_press: e => {
|
|
54844
55083
|
rest.onnavi_open_press?.(e);
|
|
54845
|
-
openController.onOpeningPress?.();
|
|
55084
|
+
openController.onOpeningPress?.(e);
|
|
54846
55085
|
},
|
|
54847
55086
|
onnavi_request_close: e => {
|
|
54848
55087
|
rest.onnavi_request_close?.(e);
|
|
@@ -54922,8 +55161,8 @@ const ExpandableUI = ({
|
|
|
54922
55161
|
onnavi_request_open: e => {
|
|
54923
55162
|
openController.open(e);
|
|
54924
55163
|
},
|
|
54925
|
-
onnavi_open_press:
|
|
54926
|
-
openController.onOpeningPress?.();
|
|
55164
|
+
onnavi_open_press: e => {
|
|
55165
|
+
openController.onOpeningPress?.(e);
|
|
54927
55166
|
},
|
|
54928
55167
|
onnavi_request_close: e => {
|
|
54929
55168
|
openController.requestClose(e, {
|
|
@@ -66976,10 +67215,10 @@ const DialogStandIn = props => {
|
|
|
66976
67215
|
}
|
|
66977
67216
|
// eslint-disable-next-line react/no-unknown-property
|
|
66978
67217
|
,
|
|
66979
|
-
onnavi_open_press:
|
|
67218
|
+
onnavi_open_press: e => {
|
|
66980
67219
|
// What the opening will read is asked for at once, as the dialog
|
|
66981
|
-
// proper does (see open_controller.js); the dialog itself
|
|
66982
|
-
//
|
|
67220
|
+
// proper does (see open_controller.js); the dialog itself once the
|
|
67221
|
+
// press is taken for one (see requestBuildOnPress) — the click that
|
|
66983
67222
|
// follows opens it built, or builds it itself when it comes sooner.
|
|
66984
67223
|
if (signal) {
|
|
66985
67224
|
preloadState(signal, readOpenValue(value));
|
|
@@ -66987,9 +67226,14 @@ const DialogStandIn = props => {
|
|
|
66987
67226
|
if (warmRef.current) {
|
|
66988
67227
|
return;
|
|
66989
67228
|
}
|
|
66990
|
-
warmRef.current =
|
|
66991
|
-
|
|
66992
|
-
|
|
67229
|
+
warmRef.current = requestBuildOnPress(e, {
|
|
67230
|
+
onBuild: () => {
|
|
67231
|
+
warmRef.current = null;
|
|
67232
|
+
build("press");
|
|
67233
|
+
},
|
|
67234
|
+
onGiveUp: () => {
|
|
67235
|
+
warmRef.current = null;
|
|
67236
|
+
}
|
|
66993
67237
|
});
|
|
66994
67238
|
}
|
|
66995
67239
|
});
|
|
@@ -67068,8 +67312,8 @@ const UncontrolledDialog = props => {
|
|
|
67068
67312
|
// The press announcing that request (see announceOpeningPress in
|
|
67069
67313
|
// commands.js), heard where the request is.
|
|
67070
67314
|
,
|
|
67071
|
-
onnavi_open_press:
|
|
67072
|
-
openController.onOpeningPress?.();
|
|
67315
|
+
onnavi_open_press: e => {
|
|
67316
|
+
openController.onOpeningPress?.(e);
|
|
67073
67317
|
},
|
|
67074
67318
|
onnavi_request_close: e => {
|
|
67075
67319
|
const closing = openController.requestClose(e, {
|
|
@@ -68870,8 +69114,8 @@ const UncontrolledPopover = props => {
|
|
|
68870
69114
|
// The press announcing that request (see announceOpeningPress in
|
|
68871
69115
|
// commands.js), heard where the request is.
|
|
68872
69116
|
,
|
|
68873
|
-
onnavi_open_press:
|
|
68874
|
-
openController.onOpeningPress?.();
|
|
69117
|
+
onnavi_open_press: e => {
|
|
69118
|
+
openController.onOpeningPress?.(e);
|
|
68875
69119
|
},
|
|
68876
69120
|
onnavi_request_close: e => {
|
|
68877
69121
|
const closing = openController.requestClose(e, {
|
|
@@ -70276,15 +70520,15 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
|
70276
70520
|
display: flex;
|
|
70277
70521
|
}
|
|
70278
70522
|
|
|
70523
|
+
&:has( > .navi_list_container:not([data-scroller], [data-overflow-visible])) {
|
|
70524
|
+
overflow: visible;
|
|
70525
|
+
}
|
|
70526
|
+
|
|
70279
70527
|
& .navi_list_container {
|
|
70280
70528
|
--list-border-radius: max(0px,
|
|
70281
70529
|
var(--popover-border-radius) - var(--popover-border-width));
|
|
70282
70530
|
overscroll-behavior: none;
|
|
70283
70531
|
width: 100%;
|
|
70284
|
-
|
|
70285
|
-
&:not([data-overflow-visible]) {
|
|
70286
|
-
overflow: auto;
|
|
70287
|
-
}
|
|
70288
70532
|
}
|
|
70289
70533
|
}
|
|
70290
70534
|
|
|
@@ -70303,15 +70547,15 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
|
70303
70547
|
flex-direction: column;
|
|
70304
70548
|
}
|
|
70305
70549
|
|
|
70550
|
+
&:has( > .navi_list_container:not([data-scroller], [data-overflow-visible])) {
|
|
70551
|
+
overflow: visible;
|
|
70552
|
+
}
|
|
70553
|
+
|
|
70306
70554
|
& .navi_list_container {
|
|
70307
70555
|
--list-border-radius: max(0px,
|
|
70308
70556
|
var(--dialog-border-radius) - var(--dialog-border-width));
|
|
70309
70557
|
overscroll-behavior: none;
|
|
70310
70558
|
width: 100%;
|
|
70311
|
-
|
|
70312
|
-
&:not([data-overflow-visible]) {
|
|
70313
|
-
overflow: auto;
|
|
70314
|
-
}
|
|
70315
70559
|
}
|
|
70316
70560
|
}
|
|
70317
70561
|
`;
|
|
@@ -70757,8 +71001,8 @@ const PickerCustom = props => {
|
|
|
70757
71001
|
},
|
|
70758
71002
|
// And the press announcing that request (see announceOpeningPress in
|
|
70759
71003
|
// commands.js), for the popup's content to be built on it.
|
|
70760
|
-
"onnavi_open_press":
|
|
70761
|
-
dispatchCustomEvent(popupRef.current, "navi_open_press");
|
|
71004
|
+
"onnavi_open_press": e => {
|
|
71005
|
+
dispatchCustomEvent(popupRef.current, "navi_open_press", e.detail);
|
|
70762
71006
|
},
|
|
70763
71007
|
"onnavi_request_close": e => {
|
|
70764
71008
|
const closing = dispatchCustomEvent(popupRef.current, "navi_request_close", e.detail);
|
|
@@ -70821,8 +71065,8 @@ const PickerCustom = props => {
|
|
|
70821
71065
|
// The press announcing that request (see announceOpeningPress in
|
|
70822
71066
|
// commands.js), heard where the request is — here too for the same
|
|
70823
71067
|
// reason. Nothing to gate: the announcer asked before saying it.
|
|
70824
|
-
onnavi_open_press:
|
|
70825
|
-
openController.onOpeningPress?.();
|
|
71068
|
+
onnavi_open_press: e => {
|
|
71069
|
+
openController.onOpeningPress?.(e);
|
|
70826
71070
|
},
|
|
70827
71071
|
onnavi_request_close: e => {
|
|
70828
71072
|
requestInteraction({
|
|
@@ -70975,7 +71219,9 @@ const PickerCustom = props => {
|
|
|
70975
71219
|
})) {
|
|
70976
71220
|
return;
|
|
70977
71221
|
}
|
|
70978
|
-
dispatchCustomEvent(pickerEl, "navi_open_press"
|
|
71222
|
+
dispatchCustomEvent(pickerEl, "navi_open_press", {
|
|
71223
|
+
event: pointerdownEvent
|
|
71224
|
+
});
|
|
70979
71225
|
};
|
|
70980
71226
|
}
|
|
70981
71227
|
let interactions = props.interactions;
|
|
@@ -73800,15 +74046,13 @@ const css$x = /* css */`@property --list-group-label-height {
|
|
|
73800
74046
|
flex-direction: column;
|
|
73801
74047
|
transition: opacity .2s;
|
|
73802
74048
|
display: flex;
|
|
73803
|
-
overflow: hidden;
|
|
73804
74049
|
|
|
73805
|
-
|
|
73806
|
-
|
|
73807
|
-
overflow: visible;
|
|
74050
|
+
&:not([data-borderless], [data-overflow-visible], [data-scroller]) {
|
|
74051
|
+
min-height: 0;
|
|
73808
74052
|
}
|
|
73809
74053
|
|
|
73810
|
-
&[data-
|
|
73811
|
-
|
|
74054
|
+
&[data-borderless] {
|
|
74055
|
+
--x-list-border-radius: 0px;
|
|
73812
74056
|
}
|
|
73813
74057
|
|
|
73814
74058
|
&[data-overflow-visible="x"] {
|