@jsenv/navi 0.29.52 → 0.29.54
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/jsenv_navi.js +461 -141
- package/dist/jsenv_navi.js.map +38 -23
- package/dist/jsenv_navi_side_effects.js +131 -12
- package/dist/jsenv_navi_side_effects.js.map +4 -2
- package/docs/AI_INSTRUCTIONS.md +38 -0
- package/docs/MOBILE_LAYOUT_PITFALLS.md +2 -2
- package/docs/actions.md +3 -1
- package/docs/create_and_edit.md +3 -1
- package/docs/css_architecture.md +10 -8
- package/docs/error_handling.md +204 -0
- package/docs/navigation.md +27 -6
- package/docs/safe_area.md +161 -0
- package/docs/scroll.md +10 -5
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -13083,6 +13083,117 @@ const setActionPrivateProperties = (action, properties) => {
|
|
|
13083
13083
|
actionPrivatePropertiesWeakMap.set(action, properties);
|
|
13084
13084
|
};
|
|
13085
13085
|
|
|
13086
|
+
/**
|
|
13087
|
+
* Where an action error goes when nothing displays it.
|
|
13088
|
+
*
|
|
13089
|
+
* An action that fails writes the error into its `errorSignal` and stops there.
|
|
13090
|
+
* It cannot know whether a screen is going to show it: at the instant it fails,
|
|
13091
|
+
* the screen that will is often not even mounted — a route action runs before
|
|
13092
|
+
* its page renders, which is precisely the case a guess made at failure time
|
|
13093
|
+
* gets wrong. So nothing is guessed. The error is let go, and whoever displays
|
|
13094
|
+
* it SAYS so by marking it; what nobody ever took is reported as unhandled.
|
|
13095
|
+
*
|
|
13096
|
+
* The mark is `__handled_by__`, the same one the jsenv supervisor reads to stay
|
|
13097
|
+
* out of the way of an error the app is already showing — one mark, one meaning:
|
|
13098
|
+
* "this is on screen somewhere".
|
|
13099
|
+
*
|
|
13100
|
+
* The whole picture, control errors and validation included: docs/error_handling.md
|
|
13101
|
+
*/
|
|
13102
|
+
|
|
13103
|
+
const markErrorAsDisplayedBy = (error, by) => {
|
|
13104
|
+
if (error && typeof error === "object") {
|
|
13105
|
+
error.__handled_by__ = by;
|
|
13106
|
+
}
|
|
13107
|
+
};
|
|
13108
|
+
|
|
13109
|
+
const errorIsDisplayed = (error) => {
|
|
13110
|
+
return Boolean(error && error.__handled_by__);
|
|
13111
|
+
};
|
|
13112
|
+
|
|
13113
|
+
/**
|
|
13114
|
+
* A render has read this error — it is now the render tree's business, not this
|
|
13115
|
+
* module's, and there is nothing left to report.
|
|
13116
|
+
*
|
|
13117
|
+
* Whatever the reader does with it is already covered without any deadline: it
|
|
13118
|
+
* displays it (and marks it), or it throws it, and a thrown error either finds a
|
|
13119
|
+
* boundary that displays it or reaches window on its own — `preact/debug`
|
|
13120
|
+
* re-throws every error a boundary caught, and an unbounded one aborts the
|
|
13121
|
+
* render loudly. Reporting it here as well would be a second voice saying the
|
|
13122
|
+
* same thing, always the wrong one, since this module cannot see which of those
|
|
13123
|
+
* happened.
|
|
13124
|
+
*/
|
|
13125
|
+
const errorTakenByRenderSet = new WeakSet();
|
|
13126
|
+
const markErrorAsTakenByRender = (error) => {
|
|
13127
|
+
if (error && typeof error === "object") {
|
|
13128
|
+
errorTakenByRenderSet.add(error);
|
|
13129
|
+
}
|
|
13130
|
+
};
|
|
13131
|
+
|
|
13132
|
+
/**
|
|
13133
|
+
* When the answer "nobody took it" is final.
|
|
13134
|
+
*
|
|
13135
|
+
* The floor is one macrotask: every render that could take the error — Preact's
|
|
13136
|
+
* queue, a Suspense boundary settling on the failure, the boundary above it —
|
|
13137
|
+
* happens in microtasks.
|
|
13138
|
+
*
|
|
13139
|
+
* That floor is enough for an action failing under a page that is already on
|
|
13140
|
+
* screen, and far too early for a route action: it fails ON the url change,
|
|
13141
|
+
* before its page exists, and that page cannot render until the routing that
|
|
13142
|
+
* asked for the data is over. Measured on an offline navigation, the screen
|
|
13143
|
+
* displaying the error arrived ~12ms after this deadline — so the app was told
|
|
13144
|
+
* it had displayed nothing while it was displaying it.
|
|
13145
|
+
*
|
|
13146
|
+
* The browser integration knows when the document has stopped moving and hands
|
|
13147
|
+
* that over here (see installReportDeadlineExtension); nothing else does, and
|
|
13148
|
+
* this module stays free of the DOM. Waiting longer costs nothing now that a
|
|
13149
|
+
* read is enough to call this off: what still reaches the report was read by no
|
|
13150
|
+
* render at all, and a late report about that is as good as a prompt one.
|
|
13151
|
+
*/
|
|
13152
|
+
let waitForDocumentSettled = null;
|
|
13153
|
+
const installReportDeadlineExtension = (fn) => {
|
|
13154
|
+
waitForDocumentSettled = fn;
|
|
13155
|
+
};
|
|
13156
|
+
|
|
13157
|
+
/**
|
|
13158
|
+
* Rethrown rather than logged: an error nobody took is an unhandled error, and
|
|
13159
|
+
* the runtime already knows what to do with those (window "error" event, jsenv
|
|
13160
|
+
* overlay in dev). Same trick preact/debug uses for the same reason.
|
|
13161
|
+
*/
|
|
13162
|
+
const errorReportedSet = new WeakSet();
|
|
13163
|
+
const reportErrorIfNobodyDisplaysIt = (error, { action } = {}) => {
|
|
13164
|
+
const decide = () => {
|
|
13165
|
+
if (errorIsDisplayed(error) || errorTakenByRenderSet.has(error)) {
|
|
13166
|
+
return;
|
|
13167
|
+
}
|
|
13168
|
+
if (error && typeof error === "object") {
|
|
13169
|
+
// The same error can reach here from more than one direction (the run
|
|
13170
|
+
// that produced it, the routing promise carrying it): it is one error and
|
|
13171
|
+
// it is reported once.
|
|
13172
|
+
if (errorReportedSet.has(error)) {
|
|
13173
|
+
return;
|
|
13174
|
+
}
|
|
13175
|
+
errorReportedSet.add(error);
|
|
13176
|
+
}
|
|
13177
|
+
if (action && error && typeof error === "object" && !error.action) {
|
|
13178
|
+
error.action = action;
|
|
13179
|
+
}
|
|
13180
|
+
throw error;
|
|
13181
|
+
};
|
|
13182
|
+
|
|
13183
|
+
setTimeout(() => {
|
|
13184
|
+
if (errorIsDisplayed(error) || errorTakenByRenderSet.has(error)) {
|
|
13185
|
+
// Already taken within the microtasks that followed the failure: the
|
|
13186
|
+
// common case, and there is nothing to wait for.
|
|
13187
|
+
return;
|
|
13188
|
+
}
|
|
13189
|
+
if (waitForDocumentSettled) {
|
|
13190
|
+
waitForDocumentSettled(decide);
|
|
13191
|
+
return;
|
|
13192
|
+
}
|
|
13193
|
+
decide();
|
|
13194
|
+
});
|
|
13195
|
+
};
|
|
13196
|
+
|
|
13086
13197
|
const SYMBOL_OBJECT_SIGNAL = Symbol.for("navi_object_signal");
|
|
13087
13198
|
|
|
13088
13199
|
let DEBUG$1 = false;
|
|
@@ -13916,7 +14027,6 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
13916
14027
|
const ui = {
|
|
13917
14028
|
renderLoaded: null,
|
|
13918
14029
|
renderLoadedAsync,
|
|
13919
|
-
hasRenderers: false, // Flag to track if action is bound to UI components
|
|
13920
14030
|
};
|
|
13921
14031
|
let sideEffectCleanup;
|
|
13922
14032
|
let completeSideEffectCleanup;
|
|
@@ -14058,28 +14168,27 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
14058
14168
|
return error;
|
|
14059
14169
|
}
|
|
14060
14170
|
if (DEBUG$1) {
|
|
14061
|
-
console.log(
|
|
14062
|
-
`"${action}": failed (error: ${error}, handled by ui: ${ui.hasRenderers})`,
|
|
14063
|
-
);
|
|
14171
|
+
console.log(`"${action}": failed (error: ${error})`);
|
|
14064
14172
|
}
|
|
14173
|
+
error.action = action;
|
|
14065
14174
|
batch(() => {
|
|
14066
14175
|
errorSignal.value = error;
|
|
14067
14176
|
runningStateSignal.value = FAILED;
|
|
14068
14177
|
onError?.(error, { event, action, args });
|
|
14069
14178
|
});
|
|
14070
14179
|
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
|
|
14077
|
-
|
|
14078
|
-
//
|
|
14079
|
-
|
|
14180
|
+
// The error is in errorSignal; from here it is the UI's, and running
|
|
14181
|
+
// the action is not the place to decide whether the UI wants it —
|
|
14182
|
+
// that answer does not exist yet at this instant (see
|
|
14183
|
+
// action_error_report.js). So the run never throws: it settles with
|
|
14184
|
+
// the error as its value, and being displayed or not is constated
|
|
14185
|
+
// afterwards, in one place, by one rule.
|
|
14186
|
+
if (onError) {
|
|
14187
|
+
// Asking for the error IS taking it.
|
|
14188
|
+
markErrorAsDisplayedBy(error, "onError");
|
|
14080
14189
|
}
|
|
14081
|
-
error
|
|
14082
|
-
|
|
14190
|
+
reportErrorIfNobodyDisplaysIt(error, { action });
|
|
14191
|
+
return error;
|
|
14083
14192
|
};
|
|
14084
14193
|
|
|
14085
14194
|
try {
|
|
@@ -14440,15 +14549,8 @@ const createActionProxyFromSignal = (
|
|
|
14440
14549
|
performReset: proxyPrivateMethod("performReset"),
|
|
14441
14550
|
ui: currentActionPrivateProperties.ui,
|
|
14442
14551
|
};
|
|
14443
|
-
onActionTargetChange((
|
|
14552
|
+
onActionTargetChange(() => {
|
|
14444
14553
|
proxyPrivateProperties.ui = currentActionPrivateProperties.ui;
|
|
14445
|
-
if (previousTarget && actionTarget) {
|
|
14446
|
-
const previousPrivateProps = getActionPrivateProperties(previousTarget);
|
|
14447
|
-
if (previousPrivateProps.ui.hasRenderers) {
|
|
14448
|
-
const newPrivateProps = getActionPrivateProperties(actionTarget);
|
|
14449
|
-
newPrivateProps.ui.hasRenderers = true;
|
|
14450
|
-
}
|
|
14451
|
-
}
|
|
14452
14554
|
proxyPrivateProperties.childActionWeakSet =
|
|
14453
14555
|
currentActionPrivateProperties.childActionWeakSet;
|
|
14454
14556
|
});
|
|
@@ -21701,7 +21803,18 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
21701
21803
|
// something at the first announcement has a definite place to give it back.
|
|
21702
21804
|
publishBeforeRouting({ url, ...options });
|
|
21703
21805
|
try {
|
|
21704
|
-
|
|
21806
|
+
const routingResult = applyRoutingTask(url, options);
|
|
21807
|
+
if (routingResult && typeof routingResult.then === "function") {
|
|
21808
|
+
// Every caller below drops this value — a click handler has nothing to
|
|
21809
|
+
// do with what the routing returns — so a rejection here would become
|
|
21810
|
+
// an anonymous unhandled one, pointing at the navigation rather than at
|
|
21811
|
+
// what failed. It goes to the single place that knows what to do with
|
|
21812
|
+
// an error nobody displays (see action_error_report.js).
|
|
21813
|
+
routingResult.catch((e) => {
|
|
21814
|
+
reportErrorIfNobodyDisplaysIt(e);
|
|
21815
|
+
});
|
|
21816
|
+
}
|
|
21817
|
+
return routingResult;
|
|
21705
21818
|
} finally {
|
|
21706
21819
|
publishAfterRouting({ url, ...options });
|
|
21707
21820
|
}
|
|
@@ -21777,6 +21890,9 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
21777
21890
|
isVisited,
|
|
21778
21891
|
state,
|
|
21779
21892
|
});
|
|
21893
|
+
if (navigationType === "push") {
|
|
21894
|
+
startAtTop(url);
|
|
21895
|
+
}
|
|
21780
21896
|
executeWithCleanup(
|
|
21781
21897
|
() => allResult,
|
|
21782
21898
|
() => {
|
|
@@ -21925,6 +22041,36 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
21925
22041
|
};
|
|
21926
22042
|
};
|
|
21927
22043
|
|
|
22044
|
+
// A page one arrives at for the first time starts at its top. Only a document
|
|
22045
|
+
// navigation does that on its own: a pushState creates its entry with whatever
|
|
22046
|
+
// scroll happened to be there, so without this the new page opens at the offset
|
|
22047
|
+
// of the one before it — and worse, that borrowed offset is what the browser
|
|
22048
|
+
// then remembers FOR that entry, and hands back on the way forward.
|
|
22049
|
+
//
|
|
22050
|
+
// Push only. A traverse is the browser's business and it is already right: it
|
|
22051
|
+
// keeps a position per entry and restores it. A replace is not an arrival —
|
|
22052
|
+
// it is the same place, said differently (a tab row travelling, see
|
|
22053
|
+
// route_travel.jsx), and resetting there would throw the reader out of a page
|
|
22054
|
+
// they never left.
|
|
22055
|
+
//
|
|
22056
|
+
// After the routes have been told, and that ordering is the whole subtlety:
|
|
22057
|
+
// the routes changing is what sets a travel off, and a travel measures the box
|
|
22058
|
+
// it is leaving as it stands. Reset before that and the picture of the page
|
|
22059
|
+
// being left is taken at the top of a page the reader was not at the top of —
|
|
22060
|
+
// it is then watched jumping back to its first line before it even begins to
|
|
22061
|
+
// leave (see holdTravelGeometry in route_travel.jsx). After pushState too, so
|
|
22062
|
+
// the entry being left keeps the offset it is at.
|
|
22063
|
+
//
|
|
22064
|
+
// The document, because the document is the scrollport in the common case. An
|
|
22065
|
+
// app that scrolls an element of its own scrolls it itself.
|
|
22066
|
+
const startAtTop = (url) => {
|
|
22067
|
+
// A fragment names where to land, and the browser is the one that finds it.
|
|
22068
|
+
if (new URL(url, window.location.href).hash) {
|
|
22069
|
+
return;
|
|
22070
|
+
}
|
|
22071
|
+
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
22072
|
+
};
|
|
22073
|
+
|
|
21928
22074
|
let updateRoutes;
|
|
21929
22075
|
|
|
21930
22076
|
const applyActions = (params) => {
|
|
@@ -22004,6 +22150,39 @@ const browserIntegration = setupBrowserIntegrationViaHistory({
|
|
|
22004
22150
|
isRouting: () => Boolean(updateRoutes),
|
|
22005
22151
|
});
|
|
22006
22152
|
|
|
22153
|
+
/**
|
|
22154
|
+
* How long an error that nothing displayed is given before it is called
|
|
22155
|
+
* unhandled (see action_error_report.js, which knows the rule but not the DOM).
|
|
22156
|
+
*
|
|
22157
|
+
* A route action fails ON the url change, before its page exists: it is the
|
|
22158
|
+
* routing itself that will bring what displays the error, so the answer is only
|
|
22159
|
+
* final once the document has stopped moving AND the frame that paints what it
|
|
22160
|
+
* brought has been through. Anything faster tells an app displaying "you are
|
|
22161
|
+
* offline" that it displayed nothing.
|
|
22162
|
+
*/
|
|
22163
|
+
installReportDeadlineExtension((decide) => {
|
|
22164
|
+
const whenPainted = () => {
|
|
22165
|
+
// The frame that paints what routing brought, then the microtasks after it:
|
|
22166
|
+
// a render claiming the error is on either side of that paint, never later.
|
|
22167
|
+
requestAnimationFrame(() => {
|
|
22168
|
+
setTimeout(decide);
|
|
22169
|
+
});
|
|
22170
|
+
};
|
|
22171
|
+
if (!documentIsBusySignal.peek()) {
|
|
22172
|
+
whenPainted();
|
|
22173
|
+
return;
|
|
22174
|
+
}
|
|
22175
|
+
// Busy right now, so the synchronous first callback of subscribe() says
|
|
22176
|
+
// "busy" and is skipped; what unsubscribes below is a later one.
|
|
22177
|
+
const unsubscribe = documentIsBusySignal.subscribe((documentIsBusy) => {
|
|
22178
|
+
if (documentIsBusy) {
|
|
22179
|
+
return;
|
|
22180
|
+
}
|
|
22181
|
+
unsubscribe();
|
|
22182
|
+
whenPainted();
|
|
22183
|
+
});
|
|
22184
|
+
});
|
|
22185
|
+
|
|
22007
22186
|
setOnAllRouteReady((v) => {
|
|
22008
22187
|
updateRoutes = v;
|
|
22009
22188
|
browserIntegration.init();
|
|
@@ -32227,18 +32406,6 @@ const ActionRenderer = ({
|
|
|
32227
32406
|
} = useActionStatus(action);
|
|
32228
32407
|
const UIRenderedPromise = useUIRenderedPromise(action);
|
|
32229
32408
|
const [errorBoundary, resetErrorBoundary] = useErrorBoundary();
|
|
32230
|
-
|
|
32231
|
-
// Mark this action as bound to UI components (has renderers)
|
|
32232
|
-
// This tells the action system that errors should be caught and stored
|
|
32233
|
-
// in the action's error state rather than bubbling up
|
|
32234
|
-
useLayoutEffect(() => {
|
|
32235
|
-
if (action) {
|
|
32236
|
-
const {
|
|
32237
|
-
ui
|
|
32238
|
-
} = getActionPrivateProperties(action);
|
|
32239
|
-
ui.hasRenderers = true;
|
|
32240
|
-
}
|
|
32241
|
-
}, [action]);
|
|
32242
32409
|
useLayoutEffect(() => {
|
|
32243
32410
|
resetErrorBoundary();
|
|
32244
32411
|
}, [action, loading, idle, resetErrorBoundary]);
|
|
@@ -32266,6 +32433,8 @@ const ActionRenderer = ({
|
|
|
32266
32433
|
return renderIdle(action);
|
|
32267
32434
|
}
|
|
32268
32435
|
if (errorBoundary) {
|
|
32436
|
+
// Displaying it is what makes it handled (see action_error_report.js)
|
|
32437
|
+
markErrorAsDisplayedBy(errorBoundary, "<ActionRenderer>");
|
|
32269
32438
|
return renderError(errorBoundary, "ui_error", action);
|
|
32270
32439
|
}
|
|
32271
32440
|
if (aborted) {
|
|
@@ -32291,6 +32460,7 @@ const ActionRenderer = ({
|
|
|
32291
32460
|
return renderLoading(action);
|
|
32292
32461
|
}
|
|
32293
32462
|
if (error) {
|
|
32463
|
+
markErrorAsDisplayedBy(error, "<ActionRenderer>");
|
|
32294
32464
|
return renderError(error, "action_error", action);
|
|
32295
32465
|
}
|
|
32296
32466
|
return renderCompletedSafe(data, action);
|
|
@@ -35772,14 +35942,22 @@ const useActionAsyncData = (action, {
|
|
|
35772
35942
|
throw dismissedPromise;
|
|
35773
35943
|
}
|
|
35774
35944
|
const actionError = action.errorSignal.peek();
|
|
35945
|
+
// A render has it now, whichever way it goes from here (see
|
|
35946
|
+
// action_error_report.js): displayed below, or thrown to a boundary that
|
|
35947
|
+
// displays it — and if none does, the throw reaches window on its own.
|
|
35948
|
+
markErrorAsTakenByRender(actionError);
|
|
35775
35949
|
if (errorEffect === "use") {
|
|
35950
|
+
// Handed to the component, which is what displays it from here on
|
|
35951
|
+
// (see action_error_report.js)
|
|
35952
|
+
markErrorAsDisplayedBy(actionError, "useAsyncData({ error: true })");
|
|
35776
35953
|
const dismissError = () => {
|
|
35777
35954
|
dismissedActionWeakSet.add(action);
|
|
35778
35955
|
setTick(n => n + 1);
|
|
35779
35956
|
};
|
|
35780
35957
|
return [undefined, false, actionError, dismissError];
|
|
35781
35958
|
}
|
|
35782
|
-
|
|
35959
|
+
// Not marked: nothing is displayed yet — the boundary that catches this is
|
|
35960
|
+
// what says so, and only if it has something to show.
|
|
35783
35961
|
throw actionError;
|
|
35784
35962
|
}
|
|
35785
35963
|
|
|
@@ -35917,8 +36095,34 @@ const LoadingFallback = ({
|
|
|
35917
36095
|
};
|
|
35918
36096
|
|
|
35919
36097
|
// ─── ErrorBoundary ────────────────────────────────────────────────────────────
|
|
35920
|
-
|
|
35921
|
-
|
|
36098
|
+
/**
|
|
36099
|
+
* Displays what its subtree throws — an action failure delegated by
|
|
36100
|
+
* `useAsyncData`, or any render error under it.
|
|
36101
|
+
*
|
|
36102
|
+
* Two things it gets right that a hand-written boundary rarely does, both
|
|
36103
|
+
* explained in docs/error_handling.md:
|
|
36104
|
+
*
|
|
36105
|
+
* - It marks the error as displayed ONLY when it actually displays it.
|
|
36106
|
+
* `preact/debug` rethrows every error a boundary caught in a `setTimeout`, on
|
|
36107
|
+
* purpose (React devtools compatibility), so a handled error still reaches
|
|
36108
|
+
* window and the jsenv overlay covers the app unless `__handled_by__` is set.
|
|
36109
|
+
* Setting it before knowing whether anything is rendered turns a boundary into
|
|
36110
|
+
* a bug swallower: a TypeError in a component becomes a blank page AND a
|
|
36111
|
+
* silent one. Without a `fallback` there is nothing to display, so the error is
|
|
36112
|
+
* left alone and continues up.
|
|
36113
|
+
*
|
|
36114
|
+
* - It resets on navigation, not only on rerun. Rerunning the failed action is
|
|
36115
|
+
* one way out; going somewhere else is the common one. Without a reset on the
|
|
36116
|
+
* document URL, the error stays in place of every page after it, including the
|
|
36117
|
+
* ones that would render fine.
|
|
36118
|
+
*
|
|
36119
|
+
* @param {object} props
|
|
36120
|
+
* @param {Function|import("ignore:preact").VNode} [props.fallback] - what is displayed
|
|
36121
|
+
* instead of the children: an element, or a component receiving
|
|
36122
|
+
* `{ error, resetError }`. Without it the boundary is transparent.
|
|
36123
|
+
* @param {() => void} [props.onReset] - called when the fallback dismisses the
|
|
36124
|
+
* error via its `resetError`.
|
|
36125
|
+
*/
|
|
35922
36126
|
const ErrorBoundary = ({
|
|
35923
36127
|
children,
|
|
35924
36128
|
fallback,
|
|
@@ -35932,9 +36136,30 @@ const ErrorBoundary = ({
|
|
|
35932
36136
|
cleanupRef.current?.();
|
|
35933
36137
|
};
|
|
35934
36138
|
}, []);
|
|
35935
|
-
if (error) {
|
|
35936
|
-
error.__handled_by__ = "<ErrorBoundary>"; // prevent jsenv from displaying it
|
|
35937
36139
|
|
|
36140
|
+
// The error belongs to the page that failed: leaving it means leaving it
|
|
36141
|
+
// behind.
|
|
36142
|
+
useEffect(() => {
|
|
36143
|
+
if (!error) {
|
|
36144
|
+
return undefined;
|
|
36145
|
+
}
|
|
36146
|
+
const documentUrlWhenCaught = documentUrlSignal.peek();
|
|
36147
|
+
return documentUrlSignal.subscribe(documentUrl => {
|
|
36148
|
+
// subscribe() calls back synchronously with the current value
|
|
36149
|
+
if (documentUrl === documentUrlWhenCaught) {
|
|
36150
|
+
return;
|
|
36151
|
+
}
|
|
36152
|
+
setDismissed(false);
|
|
36153
|
+
resetError();
|
|
36154
|
+
});
|
|
36155
|
+
}, [error]);
|
|
36156
|
+
if (error) {
|
|
36157
|
+
if (!fallback) {
|
|
36158
|
+
// Nothing to display means nothing handled: rethrow untouched so the
|
|
36159
|
+
// error reaches whoever can do something with it (an outer boundary, or
|
|
36160
|
+
// the dev overlay).
|
|
36161
|
+
throw error;
|
|
36162
|
+
}
|
|
35938
36163
|
const action = error.action;
|
|
35939
36164
|
if (action) {
|
|
35940
36165
|
cleanupRef.current?.();
|
|
@@ -35964,9 +36189,7 @@ const ErrorBoundary = ({
|
|
|
35964
36189
|
setDismissed(true);
|
|
35965
36190
|
resetError();
|
|
35966
36191
|
};
|
|
35967
|
-
|
|
35968
|
-
return null;
|
|
35969
|
-
}
|
|
36192
|
+
markErrorAsDisplayedBy(error, "<ErrorBoundary>"); // displayed here, so nothing else has to
|
|
35970
36193
|
if (typeof fallback === "function") {
|
|
35971
36194
|
return h(fallback, {
|
|
35972
36195
|
error,
|
|
@@ -37368,10 +37591,24 @@ const debug$1 = (...args) => {
|
|
|
37368
37591
|
}
|
|
37369
37592
|
};
|
|
37370
37593
|
|
|
37371
|
-
|
|
37372
|
-
|
|
37373
|
-
|
|
37374
|
-
|
|
37594
|
+
/**
|
|
37595
|
+
* Dispatches on its props:
|
|
37596
|
+
* - children → RouteContainer (traverses children statically, renders active branch)
|
|
37597
|
+
* - route → RouteLeafRoute (rendered by parent container when URL matches)
|
|
37598
|
+
* - fallback → RouteActive (rendered by parent container when no sibling matches)
|
|
37599
|
+
*
|
|
37600
|
+
* @param {object} props
|
|
37601
|
+
* @param {object} [props.route] - the route this branch is for, from `route()`
|
|
37602
|
+
* @param {object} [props.routeParams] - selects a branch on a param of that route
|
|
37603
|
+
* @param {boolean} [props.fallback] - the branch taken when no sibling matches
|
|
37604
|
+
* @param {Function|import("ignore:preact").VNode} [props.element] - what the branch renders
|
|
37605
|
+
* @param {object} [props.elementProps] - props given to `element`
|
|
37606
|
+
*
|
|
37607
|
+
* A branch says what it renders, not what happens when it cannot: loading and
|
|
37608
|
+
* error states are delegated to `<Loading>` and `<ErrorBoundary>` ancestors,
|
|
37609
|
+
* which may be written between routes (a container reads through them, see
|
|
37610
|
+
* collectBranches).
|
|
37611
|
+
*/
|
|
37375
37612
|
const Route = props => {
|
|
37376
37613
|
if (props.children) {
|
|
37377
37614
|
return jsx(RouteContainer, {
|
|
@@ -37413,6 +37650,9 @@ const collectRoutePages = children => {
|
|
|
37413
37650
|
return;
|
|
37414
37651
|
}
|
|
37415
37652
|
if (child.type !== Route) {
|
|
37653
|
+
// Something written between routes — <Loading>, <ErrorBoundary>, a box of
|
|
37654
|
+
// the app's own. The pages are inside it (see collectBranches).
|
|
37655
|
+
visit(child.props && child.props.children);
|
|
37416
37656
|
return;
|
|
37417
37657
|
}
|
|
37418
37658
|
const {
|
|
@@ -37475,8 +37715,8 @@ const RouteContainer = ({
|
|
|
37475
37715
|
return content;
|
|
37476
37716
|
};
|
|
37477
37717
|
// Walk JSX children vnodes (without rendering) to build a branch list and
|
|
37478
|
-
// find the active one in the same pass.
|
|
37479
|
-
//
|
|
37718
|
+
// find the active one in the same pass. Anything that is not a <Route> is read
|
|
37719
|
+
// through and kept around the branch it holds (see below).
|
|
37480
37720
|
// Returns { matchingBranch, fallbackBranch, activeBranch }.
|
|
37481
37721
|
const collectBranches = children => {
|
|
37482
37722
|
let matchingBranch = null;
|
|
@@ -37492,7 +37732,31 @@ const collectBranches = children => {
|
|
|
37492
37732
|
return;
|
|
37493
37733
|
}
|
|
37494
37734
|
if (child.type !== Route) {
|
|
37495
|
-
|
|
37735
|
+
// Anything else is a wrapper around branches, and the two that matter are
|
|
37736
|
+
// navi's own: a page says what it renders and delegates what it cannot —
|
|
37737
|
+
// loading to <Loading>, failing to <ErrorBoundary> — so those are written
|
|
37738
|
+
// BETWEEN the container and its routes. Reading through them is what lets
|
|
37739
|
+
// a subtree of pages share one, instead of the router demanding that its
|
|
37740
|
+
// children be routes and pushing every boundary outside of it.
|
|
37741
|
+
//
|
|
37742
|
+
// The wrapper is kept around whatever it holds: the container renders the
|
|
37743
|
+
// active branch alone, so the branch has to carry the wrapper with it, or
|
|
37744
|
+
// being selected would mean losing what was written around it.
|
|
37745
|
+
const wrapperChildren = child.props && child.props.children;
|
|
37746
|
+
if (!wrapperChildren) {
|
|
37747
|
+
throw new Error(`A <Route> child must be a <Route>, or hold some: ${String(child.type?.name ?? child.type)} holds nothing.`);
|
|
37748
|
+
}
|
|
37749
|
+
const {
|
|
37750
|
+
matchingBranch: matchingInside,
|
|
37751
|
+
fallbackBranch: fallbackInside
|
|
37752
|
+
} = collectBranches(wrapperChildren);
|
|
37753
|
+
if (matchingInside && !matchingBranch) {
|
|
37754
|
+
matchingBranch = wrapBranch(matchingInside, child);
|
|
37755
|
+
}
|
|
37756
|
+
if (fallbackInside && !fallbackBranch) {
|
|
37757
|
+
fallbackBranch = wrapBranch(fallbackInside, child);
|
|
37758
|
+
}
|
|
37759
|
+
return;
|
|
37496
37760
|
}
|
|
37497
37761
|
const {
|
|
37498
37762
|
children: nodeChildren,
|
|
@@ -37549,6 +37813,12 @@ const collectBranches = children => {
|
|
|
37549
37813
|
activeBranch
|
|
37550
37814
|
};
|
|
37551
37815
|
};
|
|
37816
|
+
const wrapBranch = (branch, wrapper) => {
|
|
37817
|
+
return {
|
|
37818
|
+
...branch,
|
|
37819
|
+
node: cloneElement(wrapper, null, branch.node)
|
|
37820
|
+
};
|
|
37821
|
+
};
|
|
37552
37822
|
const RouteLeaf = props => {
|
|
37553
37823
|
if (props.route) {
|
|
37554
37824
|
return jsx(RouteLeafRoute, {
|
|
@@ -37644,6 +37914,19 @@ const DRAGGED_ATTRIBUTE = "data-navi-route-travel-dragged";
|
|
|
37644
37914
|
const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
|
|
37645
37915
|
// The name the box wears while it travels, and only then (see nameForTravel).
|
|
37646
37916
|
const TRAVEL_NAME = "navi-route-travel";
|
|
37917
|
+
// Where the two boxes of a travel stand in the window, published for the
|
|
37918
|
+
// length of it. Measurements only: what is DERIVED from them — where a picture
|
|
37919
|
+
// goes, what a bar covers — is derived in the CSS below, so the app's own
|
|
37920
|
+
// numbers (the room its fixed bars take) can take part in it. Only the
|
|
37921
|
+
// measuring needs JS, and only for the one moment both boxes exist (see
|
|
37922
|
+
// holdTravelGeometry).
|
|
37923
|
+
const TRAVEL_TOP_PROPERTY = "--navi-route-travel-top";
|
|
37924
|
+
const TRAVEL_LEFT_PROPERTY = "--navi-route-travel-left";
|
|
37925
|
+
const TRAVEL_WIDTH_PROPERTY = "--navi-route-travel-width";
|
|
37926
|
+
const TRAVEL_HEIGHT_PROPERTY = "--navi-route-travel-height";
|
|
37927
|
+
const TRAVEL_OLD_TOP_PROPERTY = "--navi-route-travel-old-top";
|
|
37928
|
+
const TRAVEL_OLD_LEFT_PROPERTY = "--navi-route-travel-old-left";
|
|
37929
|
+
const TRAVEL_GEOMETRY_PROPERTIES = [TRAVEL_TOP_PROPERTY, TRAVEL_LEFT_PROPERTY, TRAVEL_WIDTH_PROPERTY, TRAVEL_HEIGHT_PROPERTY, TRAVEL_OLD_TOP_PROPERTY, TRAVEL_OLD_LEFT_PROPERTY];
|
|
37647
37930
|
const css$R = /* css */`
|
|
37648
37931
|
/* The name that makes the page inside this box a picture of its own during a
|
|
37649
37932
|
transition — rather than part of the one big picture the document takes, so
|
|
@@ -37716,6 +37999,23 @@ const css$R = /* css */`
|
|
|
37716
37999
|
same page changing its mind. */
|
|
37717
38000
|
mix-blend-mode: normal;
|
|
37718
38001
|
}
|
|
38002
|
+
&::view-transition-old(navi-route-travel) {
|
|
38003
|
+
/* Where the page being left WAS on screen, which is not where the group
|
|
38004
|
+
stands: the group is at the arriving box (its position animation is
|
|
38005
|
+
dropped along with its height one, below), and the two boxes are at the
|
|
38006
|
+
same place in the layout without being at the same place in the window
|
|
38007
|
+
— one page is scrolled and the other is not, so the box being left
|
|
38008
|
+
starts higher up. Left at the group's own corner the page being left
|
|
38009
|
+
would be seen jumping back to its top before it even begins to leave.
|
|
38010
|
+
Offset here rather than by \`translate\`, which the movement itself uses,
|
|
38011
|
+
and at its own size rather than the group's so that nothing is cut off
|
|
38012
|
+
the far side of the shift (see holdTravelGeometry). */
|
|
38013
|
+
top: calc(var(${TRAVEL_OLD_TOP_PROPERTY}) - var(${TRAVEL_TOP_PROPERTY}));
|
|
38014
|
+
left: calc(
|
|
38015
|
+
var(${TRAVEL_OLD_LEFT_PROPERTY}) - var(${TRAVEL_LEFT_PROPERTY})
|
|
38016
|
+
);
|
|
38017
|
+
width: auto;
|
|
38018
|
+
}
|
|
37719
38019
|
/* The pages are cut at the edge of the box they travel in. Said HERE and
|
|
37720
38020
|
nowhere else: these pictures are drawn in the top layer, so no overflow
|
|
37721
38021
|
on any element of the document — not the box's own, not a frame around
|
|
@@ -37727,7 +38027,7 @@ const css$R = /* css */`
|
|
|
37727
38027
|
}
|
|
37728
38028
|
&::view-transition-group(navi-route-travel) {
|
|
37729
38029
|
/* The window the two pictures are seen through, held still for the whole
|
|
37730
|
-
travel at the taller of the two boxes (see
|
|
38030
|
+
travel at the taller of the two boxes (see holdTravelGeometry): the group
|
|
37731
38031
|
is what CLIPS, and the browser animates its height from the box being
|
|
37732
38032
|
left to the box arriving — so the window shrinks under the pictures and
|
|
37733
38033
|
cuts the page leaving from the bottom, progressively. The box does end
|
|
@@ -37738,7 +38038,44 @@ const css$R = /* css */`
|
|
|
37738
38038
|
winning against it with !important — which also drops its position
|
|
37739
38039
|
animation, fine while a travel box stands in the same place from one
|
|
37740
38040
|
route to the next. */
|
|
37741
|
-
height: var(
|
|
38041
|
+
height: var(${TRAVEL_HEIGHT_PROPERTY});
|
|
38042
|
+
|
|
38043
|
+
/* Cut at the safe area, on top of being cut at the box. The pictures are
|
|
38044
|
+
drawn in the top layer, so they cover a fixed bar as easily as anything
|
|
38045
|
+
else — and the box they travel in runs UNDER the bars by design: that
|
|
38046
|
+
is what a fixed bar is for, and what the room it gives back is for. A
|
|
38047
|
+
box scrolled by so much as a pixel therefore starts above the top bar
|
|
38048
|
+
and ends below the bottom one, and the travel would be watched painting
|
|
38049
|
+
over both for its whole length.
|
|
38050
|
+
|
|
38051
|
+
The band left free is the app's own safe area (see layout/safe_area.js)
|
|
38052
|
+
— every kind of furniture at once, not the bars alone, and read rather
|
|
38053
|
+
than asked for, so one that grows, shrinks or unmounts mid-travel is
|
|
38054
|
+
followed without anything being told. What the group cannot know is
|
|
38055
|
+
only where it itself stands, and that is the measured half. */
|
|
38056
|
+
--navi-route-travel-clip-top: max(
|
|
38057
|
+
0px,
|
|
38058
|
+
var(--navi-safe-area-inset-top) - var(${TRAVEL_TOP_PROPERTY})
|
|
38059
|
+
);
|
|
38060
|
+
--navi-route-travel-clip-left: max(
|
|
38061
|
+
0px,
|
|
38062
|
+
var(--navi-safe-area-inset-left) - var(${TRAVEL_LEFT_PROPERTY})
|
|
38063
|
+
);
|
|
38064
|
+
--navi-route-travel-clip-bottom: max(
|
|
38065
|
+
0px,
|
|
38066
|
+
var(${TRAVEL_TOP_PROPERTY}) + var(${TRAVEL_HEIGHT_PROPERTY}) +
|
|
38067
|
+
var(--navi-safe-area-inset-bottom) - 100dvh
|
|
38068
|
+
);
|
|
38069
|
+
--navi-route-travel-clip-right: max(
|
|
38070
|
+
0px,
|
|
38071
|
+
var(${TRAVEL_LEFT_PROPERTY}) + var(${TRAVEL_WIDTH_PROPERTY}) +
|
|
38072
|
+
var(--navi-safe-area-inset-right) - 100dvw
|
|
38073
|
+
);
|
|
38074
|
+
clip-path: inset(
|
|
38075
|
+
var(--navi-route-travel-clip-top) var(--navi-route-travel-clip-right)
|
|
38076
|
+
var(--navi-route-travel-clip-bottom)
|
|
38077
|
+
var(--navi-route-travel-clip-left)
|
|
38078
|
+
);
|
|
37742
38079
|
animation-duration: var(--navi-route-travel-duration, 300ms);
|
|
37743
38080
|
animation-name: none;
|
|
37744
38081
|
}
|
|
@@ -37947,9 +38284,10 @@ const css$R = /* css */`
|
|
|
37947
38284
|
* sections inside the box the whole application travels in — and the class they
|
|
37948
38285
|
* share plus their axis are not enough to tell them apart from the outside.
|
|
37949
38286
|
*
|
|
37950
|
-
* The pages are cut at the edge of this box while they travel,
|
|
37951
|
-
*
|
|
37952
|
-
*
|
|
38287
|
+
* The pages are cut at the edge of this box while they travel, and at the app's
|
|
38288
|
+
* safe area the box runs under, which is written on the transition's own
|
|
38289
|
+
* pseudo-elements — no overflow of the document reaches pictures drawn in the
|
|
38290
|
+
* top layer. It needs nothing of the browser beyond view
|
|
37953
38291
|
* transitions themselves: a browser without them (Firefox) navigates without the
|
|
37954
38292
|
* movement, and the gesture applies its change on release instead of dragging a
|
|
37955
38293
|
* picture that does not exist.
|
|
@@ -38052,8 +38390,8 @@ const RouteTravel = ({
|
|
|
38052
38390
|
}
|
|
38053
38391
|
pageAskedForRef.current = page;
|
|
38054
38392
|
// The box as it stands before anything moves: rendering is held, so this is
|
|
38055
|
-
// still the page being left (see
|
|
38056
|
-
const
|
|
38393
|
+
// still the page being left (see holdTravelGeometry).
|
|
38394
|
+
const rectBefore = elementRef.current.getBoundingClientRect();
|
|
38057
38395
|
// The hold a navigation already took, if this travel is the answer to one:
|
|
38058
38396
|
// taking another would be taking a hold on a page that is holding still.
|
|
38059
38397
|
const releaseRendering = renderingHeldForRouting || holdRendering();
|
|
@@ -38068,6 +38406,11 @@ const RouteTravel = ({
|
|
|
38068
38406
|
// screen and an error nobody asked for.
|
|
38069
38407
|
const renderWait = armRouteRenderWait();
|
|
38070
38408
|
const viewTransition = startViewTransition(async () => {
|
|
38409
|
+
// Whatever is awaited here must be able to resolve without the page being
|
|
38410
|
+
// rendered: the document is frozen for the whole of this callback, and a
|
|
38411
|
+
// frame never comes — waiting for one waits until the browser gives up on
|
|
38412
|
+
// the transition. And it stays frozen exactly this long, so this is also
|
|
38413
|
+
// the shortest thing there is to keep short.
|
|
38071
38414
|
await whilePageRenders(page, async () => {
|
|
38072
38415
|
releaseRendering();
|
|
38073
38416
|
if (change) {
|
|
@@ -38076,7 +38419,7 @@ const RouteTravel = ({
|
|
|
38076
38419
|
}, renderWait);
|
|
38077
38420
|
// The page arriving is in the DOM and the transition has not started
|
|
38078
38421
|
// playing: the one moment both boxes can be known.
|
|
38079
|
-
|
|
38422
|
+
holdTravelGeometry(elementRef.current, rectBefore);
|
|
38080
38423
|
});
|
|
38081
38424
|
travel.viewTransition = viewTransition;
|
|
38082
38425
|
if (scrub) {
|
|
@@ -38405,7 +38748,7 @@ const RouteTravel = ({
|
|
|
38405
38748
|
document.documentElement.removeAttribute(TRAVEL_AXIS_ATTRIBUTE);
|
|
38406
38749
|
document.documentElement.removeAttribute(DRAGGED_ATTRIBUTE);
|
|
38407
38750
|
document.documentElement.removeAttribute(TURNED_ATTRIBUTE);
|
|
38408
|
-
|
|
38751
|
+
releaseTravelGeometry();
|
|
38409
38752
|
}
|
|
38410
38753
|
};
|
|
38411
38754
|
|
|
@@ -38787,23 +39130,43 @@ const releaseHold = travel => {
|
|
|
38787
39130
|
travelHoldingPictures = null;
|
|
38788
39131
|
document.documentElement.removeAttribute(HOLD_ATTRIBUTE);
|
|
38789
39132
|
};
|
|
38790
|
-
|
|
38791
|
-
// The
|
|
38792
|
-
//
|
|
38793
|
-
//
|
|
38794
|
-
//
|
|
38795
|
-
//
|
|
38796
|
-
|
|
38797
|
-
|
|
38798
|
-
|
|
38799
|
-
|
|
39133
|
+
|
|
39134
|
+
// The two boxes of a travel, measured at the one moment both exist: the
|
|
39135
|
+
// arriving page is in the DOM and the transition has not started playing.
|
|
39136
|
+
//
|
|
39137
|
+
// The group stands at the ARRIVING box — its own animation is dropped, so it
|
|
39138
|
+
// takes the geometry the browser declared for it and holds it for the whole
|
|
39139
|
+
// travel. That is why both rectangles have to be published: a group that does
|
|
39140
|
+
// not move says nothing about where the page being left was, and its rectangle
|
|
39141
|
+
// in the window is the only thing CSS cannot work out on its own.
|
|
39142
|
+
const holdTravelGeometry = (element, rectBefore) => {
|
|
39143
|
+
const rectAfter = element.getBoundingClientRect();
|
|
39144
|
+
// The height it is held at is the taller of the two boxes, so neither picture
|
|
39145
|
+
// is ever cut. It cannot be measured from one side alone: a page arriving
|
|
39146
|
+
// shorter than the one it replaces would cut the one leaving, a page arriving
|
|
39147
|
+
// taller would be cut itself.
|
|
39148
|
+
const height = rectBefore.height > rectAfter.height ? rectBefore.height : rectAfter.height;
|
|
39149
|
+
const {
|
|
39150
|
+
style
|
|
39151
|
+
} = document.documentElement;
|
|
39152
|
+
style.setProperty(TRAVEL_TOP_PROPERTY, `${rectAfter.top}px`);
|
|
39153
|
+
style.setProperty(TRAVEL_LEFT_PROPERTY, `${rectAfter.left}px`);
|
|
39154
|
+
style.setProperty(TRAVEL_WIDTH_PROPERTY, `${rectAfter.width}px`);
|
|
39155
|
+
style.setProperty(TRAVEL_HEIGHT_PROPERTY, `${height}px`);
|
|
39156
|
+
style.setProperty(TRAVEL_OLD_TOP_PROPERTY, `${rectBefore.top}px`);
|
|
39157
|
+
style.setProperty(TRAVEL_OLD_LEFT_PROPERTY, `${rectBefore.left}px`);
|
|
38800
39158
|
};
|
|
38801
39159
|
// The live layout takes the box back. A discontinuity by construction — the
|
|
38802
39160
|
// group stands at the held height, the box is at the new one — and an invisible
|
|
38803
39161
|
// one: the page arriving is fully in place, and the strip below it that the
|
|
38804
39162
|
// group still covers shows the page leaving only while it is still on screen.
|
|
38805
|
-
const
|
|
38806
|
-
|
|
39163
|
+
const releaseTravelGeometry = () => {
|
|
39164
|
+
const {
|
|
39165
|
+
style
|
|
39166
|
+
} = document.documentElement;
|
|
39167
|
+
for (const property of TRAVEL_GEOMETRY_PROPERTIES) {
|
|
39168
|
+
style.removeProperty(property);
|
|
39169
|
+
}
|
|
38807
39170
|
};
|
|
38808
39171
|
|
|
38809
39172
|
// The browser does not take the picture of the page being left when a
|
|
@@ -43278,60 +43641,15 @@ const withPixelUnit = value => {
|
|
|
43278
43641
|
};
|
|
43279
43642
|
|
|
43280
43643
|
/**
|
|
43281
|
-
*
|
|
43282
|
-
*
|
|
43283
|
-
*
|
|
43284
|
-
*
|
|
43285
|
-
*
|
|
43286
|
-
*
|
|
43287
|
-
*
|
|
43288
|
-
* bar. Without it the last screenful stays covered, unreachable.
|
|
43289
|
-
* - **scroll-padding**, so anything the browser scrolls TO lands in front of
|
|
43290
|
-
* the bar rather than under it. An anchor link, `scrollIntoView()`, a focused
|
|
43291
|
-
* field brought into view, restoring a scroll position — all of them align
|
|
43292
|
-
* the target with the edge of the scrollport, which is behind the bar. The
|
|
43293
|
-
* padding above does not help here: it moves the content, not the place the
|
|
43294
|
-
* browser scrolls the target to.
|
|
43295
|
-
*
|
|
43296
|
-
* Published on <html> as CSS variables rather than applied to some element:
|
|
43297
|
-
* which element scrolls is the app's business, and an app with more than one
|
|
43298
|
-
* would have to fight a component that picked for it. The app either marks its
|
|
43299
|
-
* scrolling area with `data-navi-fixed-bar-space` (the rules below) or reads
|
|
43300
|
-
* the variables itself. `:root` gets the scroll-padding unconditionally,
|
|
43301
|
-
* because the document is the scrollport in the common case and an anchor
|
|
43302
|
-
* landing under a bar is never what anyone wants.
|
|
43303
|
-
*
|
|
43304
|
-
* The variables hold the measured size of the bars on that edge — see the
|
|
43305
|
-
* comment where FixedBar sets them.
|
|
43644
|
+
* How much room the fixed bars take on each edge, published for the safe area
|
|
43645
|
+
* to add up (see layout/safe_area.js — it declares the four variables written
|
|
43646
|
+
* here, and what reads them reads the sum, never these).
|
|
43647
|
+
*
|
|
43648
|
+
* Measured rather than declared: a bar's size comes from a prop, a theme
|
|
43649
|
+
* variable, its own content or the device's notch, and only the used value
|
|
43650
|
+
* knows all four.
|
|
43306
43651
|
*/
|
|
43307
43652
|
|
|
43308
|
-
const FIXED_BAR_SPACE_CSS = /* css */ `
|
|
43309
|
-
:root {
|
|
43310
|
-
--navi-fixed-bar-space-top: 0px;
|
|
43311
|
-
--navi-fixed-bar-space-bottom: 0px;
|
|
43312
|
-
--navi-fixed-bar-space-left: 0px;
|
|
43313
|
-
--navi-fixed-bar-space-right: 0px;
|
|
43314
|
-
|
|
43315
|
-
scroll-padding-top: var(--navi-fixed-bar-space-top);
|
|
43316
|
-
scroll-padding-right: var(--navi-fixed-bar-space-right);
|
|
43317
|
-
scroll-padding-bottom: var(--navi-fixed-bar-space-bottom);
|
|
43318
|
-
scroll-padding-left: var(--navi-fixed-bar-space-left);
|
|
43319
|
-
}
|
|
43320
|
-
|
|
43321
|
-
/* Put this on whatever scrolls under the bars. */
|
|
43322
|
-
[data-navi-fixed-bar-space] {
|
|
43323
|
-
padding-top: var(--navi-fixed-bar-space-top);
|
|
43324
|
-
padding-right: var(--navi-fixed-bar-space-right);
|
|
43325
|
-
padding-bottom: var(--navi-fixed-bar-space-bottom);
|
|
43326
|
-
padding-left: var(--navi-fixed-bar-space-left);
|
|
43327
|
-
|
|
43328
|
-
scroll-padding-top: var(--navi-fixed-bar-space-top);
|
|
43329
|
-
scroll-padding-right: var(--navi-fixed-bar-space-right);
|
|
43330
|
-
scroll-padding-bottom: var(--navi-fixed-bar-space-bottom);
|
|
43331
|
-
scroll-padding-left: var(--navi-fixed-bar-space-left);
|
|
43332
|
-
}
|
|
43333
|
-
`;
|
|
43334
|
-
|
|
43335
43653
|
// Several bars can share an edge — during a page transition the outgoing and
|
|
43336
43654
|
// the incoming one are both mounted. They are all pinned to that same edge, so
|
|
43337
43655
|
// they overlap: the room to give back is the largest of them, not their sum,
|
|
@@ -43461,7 +43779,10 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
43461
43779
|
* nearest scrolling ancestor, and an app shell almost always has one (an
|
|
43462
43780
|
* `overflow` somewhere) — the bar would then stick inside that box and
|
|
43463
43781
|
* never to the window. Fixed, centered and bounded by `maxWidth`, it also
|
|
43464
|
-
* stays lined up with the content on a wide screen.
|
|
43782
|
+
* stays lined up with the content on a wide screen. It is pinned to the
|
|
43783
|
+
* app's rectangle rather than to the glass (`--navi-app-inset-*`, see
|
|
43784
|
+
* layout/safe_area.js): an app that declares itself narrower than the window
|
|
43785
|
+
* keeps its bars against its own edges.
|
|
43465
43786
|
* 2. **It gives its space back.** Being fixed it covers the content: without a
|
|
43466
43787
|
* reserve the end of a long page stays under it, unreachable. It publishes
|
|
43467
43788
|
* what it takes on <html> — see fixed_bar_space.js.
|
|
@@ -43493,8 +43814,6 @@ const css$L = /* css */`
|
|
|
43493
43814
|
}
|
|
43494
43815
|
}
|
|
43495
43816
|
|
|
43496
|
-
${FIXED_BAR_SPACE_CSS}
|
|
43497
|
-
|
|
43498
43817
|
.navi_fixed_bar {
|
|
43499
43818
|
position: fixed;
|
|
43500
43819
|
z-index: var(--navi-z-index-bar);
|
|
@@ -43509,8 +43828,8 @@ const css$L = /* css */`
|
|
|
43509
43828
|
whose padding ignored it would put its first item under it. */
|
|
43510
43829
|
&[data-area="top"],
|
|
43511
43830
|
&[data-area="bottom"] {
|
|
43512
|
-
right:
|
|
43513
|
-
left:
|
|
43831
|
+
right: var(--navi-app-inset-right);
|
|
43832
|
+
left: var(--navi-app-inset-left);
|
|
43514
43833
|
/* No width of its own: pinned to both edges, the used width absorbs the
|
|
43515
43834
|
padding instead of being inflated by it. max-width then narrows it and
|
|
43516
43835
|
the auto margins re-center it. */
|
|
@@ -43524,8 +43843,8 @@ const css$L = /* css */`
|
|
|
43524
43843
|
}
|
|
43525
43844
|
&[data-area="left"],
|
|
43526
43845
|
&[data-area="right"] {
|
|
43527
|
-
top:
|
|
43528
|
-
bottom:
|
|
43846
|
+
top: var(--navi-app-inset-top);
|
|
43847
|
+
bottom: var(--navi-app-inset-bottom);
|
|
43529
43848
|
padding-top: calc(
|
|
43530
43849
|
var(--navi-fixed-bar-padding) + env(safe-area-inset-top)
|
|
43531
43850
|
);
|
|
@@ -43539,28 +43858,28 @@ const css$L = /* css */`
|
|
|
43539
43858
|
added to the size: the background then runs under the notch while the
|
|
43540
43859
|
content keeps the whole width/height asked for. */
|
|
43541
43860
|
&[data-area="top"] {
|
|
43542
|
-
top:
|
|
43861
|
+
top: var(--navi-app-inset-top);
|
|
43543
43862
|
height: calc(var(--navi-fixed-bar-height) + env(safe-area-inset-top));
|
|
43544
43863
|
padding-top: env(safe-area-inset-top);
|
|
43545
43864
|
box-shadow: 0 var(--navi-fixed-bar-border-width) 0
|
|
43546
43865
|
var(--navi-fixed-bar-border-color);
|
|
43547
43866
|
}
|
|
43548
43867
|
&[data-area="bottom"] {
|
|
43549
|
-
bottom:
|
|
43868
|
+
bottom: var(--navi-app-inset-bottom);
|
|
43550
43869
|
height: calc(var(--navi-fixed-bar-height) + env(safe-area-inset-bottom));
|
|
43551
43870
|
padding-bottom: env(safe-area-inset-bottom);
|
|
43552
43871
|
box-shadow: 0 calc(-1 * var(--navi-fixed-bar-border-width)) 0
|
|
43553
43872
|
var(--navi-fixed-bar-border-color);
|
|
43554
43873
|
}
|
|
43555
43874
|
&[data-area="left"] {
|
|
43556
|
-
left:
|
|
43875
|
+
left: var(--navi-app-inset-left);
|
|
43557
43876
|
width: calc(var(--navi-fixed-bar-width) + env(safe-area-inset-left));
|
|
43558
43877
|
padding-left: env(safe-area-inset-left);
|
|
43559
43878
|
box-shadow: var(--navi-fixed-bar-border-width) 0 0
|
|
43560
43879
|
var(--navi-fixed-bar-border-color);
|
|
43561
43880
|
}
|
|
43562
43881
|
&[data-area="right"] {
|
|
43563
|
-
right:
|
|
43882
|
+
right: var(--navi-app-inset-right);
|
|
43564
43883
|
width: calc(var(--navi-fixed-bar-width) + env(safe-area-inset-right));
|
|
43565
43884
|
padding-right: env(safe-area-inset-right);
|
|
43566
43885
|
box-shadow: calc(-1 * var(--navi-fixed-bar-border-width)) 0 0
|
|
@@ -54760,12 +55079,13 @@ const css$v = /* css */`
|
|
|
54760
55079
|
pointer-events: none;
|
|
54761
55080
|
}
|
|
54762
55081
|
|
|
54763
|
-
/* Scrolling with the page means sticking to the viewport, and
|
|
54764
|
-
|
|
54765
|
-
|
|
54766
|
-
|
|
55082
|
+
/* Scrolling with the page means sticking to the viewport, and whatever the
|
|
55083
|
+
app puts in front of that viewport — a FixedBar, a band of its own — is
|
|
55084
|
+
in front of the label too: without the offset a sticky label lands behind
|
|
55085
|
+
it. The safe area is what that adds up to (see layout/safe_area.js) and
|
|
55086
|
+
it is 0px when nothing covers the top. */
|
|
54767
55087
|
&[data-scroller="document"] {
|
|
54768
|
-
--x-list-group-label-top: var(--navi-
|
|
55088
|
+
--x-list-group-label-top: var(--navi-safe-area-inset-top);
|
|
54769
55089
|
}
|
|
54770
55090
|
|
|
54771
55091
|
&[data-expand-x] {
|
|
@@ -71604,5 +71924,5 @@ const UserSvg = () => jsx("svg", {
|
|
|
71604
71924
|
})
|
|
71605
71925
|
});
|
|
71606
71926
|
|
|
71607
|
-
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeSpin, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
71927
|
+
export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, StarSvg, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeSpin, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, compareTwoJsValues, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, defineNaviConfirmPopupOptions, detectHorizontalOverflow, enableDebugActions, enableDebugOnDocumentLoading, ensureDocumentStartViewTransition, errorIsDisplayed, filterTableSelection, formatDatetime, formatDay, formatDayRelative, formatMonth, formatNumber, formatTime, formatTimeRelative, getNowHours, getNowHoursRoundedToStep, interpolateText, isCellSelected, isColumnSelected, isRowSelected, isScrolling, isToday, languagesSignal, localStorageSignal, markErrorAsDisplayedBy, moveArrayItemByIndex, navBack, navForward, navIntegratedVia, navTo, naviI18n, openCallout, rawUrlPart, registerGlobalConstraint, reload, rerunActions, resource, route, routeAction, scrollActivitySignal, setBaseUrl, setPreferredLanguage, setSupportedLanguages, setUrlTargetOptions, setupRoutes, smallTouchScreenSignal, stateSignal, stopLoad, stringifyTableSelectionValue, swapArrayItemByIndex, syncOwnedResourceToSignals, syncResourceToSignals, triggerNaviCommand, updateActions, useActionStatus, useArraySignalMembership, useAsyncData, useCalloutRequestClose, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
|
|
71608
71928
|
//# sourceMappingURL=jsenv_navi.js.map
|