@jsenv/navi 0.29.325 → 0.29.327
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 +78 -12
- package/dist/dev/jsenv_navi.js.map +5 -5
- package/dist/jsenv_navi.js +45 -16
- package/dist/jsenv_navi.js.map +5 -5
- package/package.json +2 -2
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -3235,6 +3235,15 @@ const resolveActionProxies = (actionSet) => {
|
|
|
3235
3235
|
|
|
3236
3236
|
const NO_PARAMS = { __no_params__: true };
|
|
3237
3237
|
const mergeActionParams = (currentParams, newParams) => {
|
|
3238
|
+
if (newParams === null) {
|
|
3239
|
+
// `null` is a question that cannot be asked: a callback destructuring its
|
|
3240
|
+
// params throws on it. It is what a computed says when it has not got
|
|
3241
|
+
// enough to ask with, and "nothing to ask" is said with `undefined`
|
|
3242
|
+
// everywhere below (and by the run guards reading paramsSignal), so it
|
|
3243
|
+
// becomes that here, at the one door params coming from a binding go
|
|
3244
|
+
// through.
|
|
3245
|
+
newParams = undefined;
|
|
3246
|
+
}
|
|
3238
3247
|
// The order of these two checks is load-bearing. Checking `undefined` first
|
|
3239
3248
|
// looks symmetric — "no new params, keep whatever is there, NO_PARAMS
|
|
3240
3249
|
// included" — and breaks routing: merge(NO_PARAMS, undefined) must yield
|
|
@@ -3492,7 +3501,10 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3492
3501
|
*
|
|
3493
3502
|
* @param {any} newParamsOrSignal - params, or a signal holding them (the
|
|
3494
3503
|
* result then retargets itself as the signal changes), or an object whose
|
|
3495
|
-
* values may be signals.
|
|
3504
|
+
* values may be signals. A signal with nothing to ask yet — a question
|
|
3505
|
+
* computed from a form still missing a field — holds `undefined` or
|
|
3506
|
+
* `null`: the instance then has no params, which is what
|
|
3507
|
+
* `useAsyncData(action, { run: true })` reads to start nothing.
|
|
3496
3508
|
* @param {object} [options]
|
|
3497
3509
|
* @param {number} [options.debounce] - milliseconds the params must stay
|
|
3498
3510
|
* stable before the instance follows them. What it buys is a screen that
|
|
@@ -11276,9 +11288,11 @@ const swipeTypeOf = (axis, pulled) => {
|
|
|
11276
11288
|
* copy is captured where it lands rather than where it was let go of.
|
|
11277
11289
|
*
|
|
11278
11290
|
* And the promise matters in both cases: the gesture holds its copy until the
|
|
11279
|
-
* answer settles. Returning the transition is what makes a landing continuous;
|
|
11280
|
-
*
|
|
11281
|
-
* screen has to say so.
|
|
11291
|
+
* answer settles. Returning the transition is what makes a landing continuous;
|
|
11292
|
+
* rejecting brings the copy back over the original, because the thing still
|
|
11293
|
+
* exists and the screen has to say so. That holds for every outcome that carries
|
|
11294
|
+
* a copy — `land` and `reorder` as much as `toss` and `leave` — so a place can be
|
|
11295
|
+
* asked about rather than taken: refuse, and the board is as it was.
|
|
11282
11296
|
*
|
|
11283
11297
|
* Starting a document transition is the application's call and not navi's: a
|
|
11284
11298
|
* `view-transition-name` must be unique per document, so only the application can
|
|
@@ -17101,6 +17115,11 @@ const generateSignalId = () => {
|
|
|
17101
17115
|
* as the user moved. `"push"` is for a state whose values ARE places one came
|
|
17102
17116
|
* from (the photo one is looking at in a gallery). Whatever is said here, one
|
|
17103
17117
|
* write can say otherwise: `signal.set(value, { history })`.
|
|
17118
|
+
* Either way every write reaches the address, synchronously, and browsers
|
|
17119
|
+
* refuse an address written too often (Safari: 100 writes per 10 s, then a
|
|
17120
|
+
* SecurityError). A value one DRAGS is written at 60 Hz: keep it in the
|
|
17121
|
+
* gesture while the finger is down and write the state once, on release —
|
|
17122
|
+
* an address is not a recording of a gesture.
|
|
17104
17123
|
* @param {boolean} [options.debug=false] - Enable debug logging for this signal's operations
|
|
17105
17124
|
* @returns {import("@preact/signals").Signal} A signal that can be synchronized with a source signal and/or persisted in localStorage. The signal includes a `validity` property for validation state.
|
|
17106
17125
|
*
|
|
@@ -27294,11 +27313,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
27294
27313
|
[NAV_DEPTH_STATE_KEY]: getNavDepth(),
|
|
27295
27314
|
},
|
|
27296
27315
|
});
|
|
27297
|
-
|
|
27298
|
-
window.history.pushState(effectiveState, null, url);
|
|
27299
|
-
} else {
|
|
27300
|
-
window.history.replaceState(effectiveState, null, url);
|
|
27301
|
-
}
|
|
27316
|
+
writeHistoryEntry(navigationType, effectiveState, url);
|
|
27302
27317
|
rememberEntryIsOfThisDocument();
|
|
27303
27318
|
updateDocumentUrl(url);
|
|
27304
27319
|
updateDocumentState(effectiveState);
|
|
@@ -27318,14 +27333,14 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
27318
27333
|
[NAV_DEPTH_STATE_KEY]: getNavDepth(),
|
|
27319
27334
|
},
|
|
27320
27335
|
});
|
|
27321
|
-
|
|
27336
|
+
writeHistoryEntry("replace", state, url);
|
|
27322
27337
|
rememberEntryIsOfThisDocument();
|
|
27323
27338
|
} else if (redirected) {
|
|
27324
27339
|
// The entry the browser is on names an address that only sends
|
|
27325
27340
|
// elsewhere — a cold load on it, or a back into it. Written over where
|
|
27326
27341
|
// it stands (the entry keeps its place in the stack, hence its state
|
|
27327
27342
|
// and the depth in it): pressing back must not walk into it again.
|
|
27328
|
-
|
|
27343
|
+
writeHistoryEntry("replace", state, url);
|
|
27329
27344
|
rememberEntryIsOfThisDocument();
|
|
27330
27345
|
}
|
|
27331
27346
|
updateDocumentUrl(url);
|
|
@@ -27578,7 +27593,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
27578
27593
|
// The entry itself has to lose them too, not just the document state:
|
|
27579
27594
|
// getDocumentState() reads the entry back, and every state write copies
|
|
27580
27595
|
// what it finds there onto the next one.
|
|
27581
|
-
|
|
27596
|
+
writeHistoryEntry("replace", state, url);
|
|
27582
27597
|
}
|
|
27583
27598
|
handleRoutingTask(url, {
|
|
27584
27599
|
reason: "routing initialization",
|
|
@@ -27605,6 +27620,57 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
27605
27620
|
// replace onto the url already displayed. Never a traverse — the browser has
|
|
27606
27621
|
// already moved window.location.href when a popstate is handled, so the
|
|
27607
27622
|
// comparison would say "same" about every back and forward.
|
|
27623
|
+
// Browsers refuse an address written too often: WebKit throws a SecurityError
|
|
27624
|
+
// past 100 writes per 10 seconds, Chromium and Firefox drop the write. A state
|
|
27625
|
+
// bound to the url and written per frame gets there in under two seconds, and
|
|
27626
|
+
// a developer working in Chromium never sees it. Every write goes through here
|
|
27627
|
+
// so the rate is measured against the strictest budget before any browser
|
|
27628
|
+
// refuses, and so a refusal names its cause rather than `replaceState`.
|
|
27629
|
+
const HISTORY_WRITE_BUDGET = 100;
|
|
27630
|
+
const HISTORY_WRITE_WINDOW_MS = 10_000;
|
|
27631
|
+
const ADDRESS_WRITE_ADVICE = `An address is not a recording of a gesture: a value dragged per frame belongs to the gesture, and the state bound to the url is written once, on release.`;
|
|
27632
|
+
const writeHistoryEntry = (navigationType, state, url) => {
|
|
27633
|
+
noteHistoryWrite(url);
|
|
27634
|
+
try {
|
|
27635
|
+
if (navigationType === "push") {
|
|
27636
|
+
window.history.pushState(state, null, url);
|
|
27637
|
+
} else {
|
|
27638
|
+
window.history.replaceState(state, null, url);
|
|
27639
|
+
}
|
|
27640
|
+
} catch (e) {
|
|
27641
|
+
if (e.name !== "SecurityError") {
|
|
27642
|
+
throw e;
|
|
27643
|
+
}
|
|
27644
|
+
throw new Error(
|
|
27645
|
+
`The browser refused to write the address ${url}: ${e.message}. ${ADDRESS_WRITE_ADVICE}`,
|
|
27646
|
+
{ cause: e },
|
|
27647
|
+
);
|
|
27648
|
+
}
|
|
27649
|
+
};
|
|
27650
|
+
const noteHistoryWrite = (() => {
|
|
27651
|
+
const writeTimes = [];
|
|
27652
|
+
let warned = false;
|
|
27653
|
+
return (url) => {
|
|
27654
|
+
if (warned) {
|
|
27655
|
+
return;
|
|
27656
|
+
}
|
|
27657
|
+
const now = performance.now();
|
|
27658
|
+
writeTimes.push(now);
|
|
27659
|
+
while (writeTimes[0] < now - HISTORY_WRITE_WINDOW_MS) {
|
|
27660
|
+
writeTimes.shift();
|
|
27661
|
+
}
|
|
27662
|
+
if (writeTimes.length < HISTORY_WRITE_BUDGET) {
|
|
27663
|
+
return;
|
|
27664
|
+
}
|
|
27665
|
+
warned = true;
|
|
27666
|
+
const seconds = ((now - writeTimes[0]) / 1000).toFixed(1);
|
|
27667
|
+
console.warn(
|
|
27668
|
+
`[navi] the address was written ${HISTORY_WRITE_BUDGET} times in ${seconds}s (last: ${url}). Safari refuses more than ${HISTORY_WRITE_BUDGET} writes per ${HISTORY_WRITE_WINDOW_MS / 1000}s with a SecurityError, and the state then runs ahead of the address. ${ADDRESS_WRITE_ADVICE}`,
|
|
27669
|
+
);
|
|
27670
|
+
};
|
|
27671
|
+
})()
|
|
27672
|
+
;
|
|
27673
|
+
|
|
27608
27674
|
const isStateOnlyNavigation = (
|
|
27609
27675
|
url,
|
|
27610
27676
|
{ navigationType },
|