@jsenv/navi 0.29.366 → 0.29.367

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.
@@ -30627,6 +30627,9 @@ const TRANSITION_TARGET_ATTRIBUTE = "data-navi-route-transition-target";
30627
30627
  // link being pressed (see <Link routeTransition>), or handed to navTo(). It answers
30628
30628
  // for that navigation and for no other — the next one is back to the relations.
30629
30629
  const TRANSITION_REQUEST_ATTRIBUTE = "data-navi-route-transition-request";
30630
+ // What a history entry remembers of the crossing that created it, in the
30631
+ // entry's own state: `{ from, type, direction, duration }` (see recordCrossing).
30632
+ const CROSSING_STATE_KEY = "jsenv_route_transition";
30630
30633
  const AREA_NAME = "navi-route-transition";
30631
30634
  // The pictures carrying the movement, among everything else it takes along —
30632
30635
  // the pages', or the document's own when the pages ARE the document (see
@@ -31175,6 +31178,106 @@ const normalizeRequest = transition => {
31175
31178
  };
31176
31179
  };
31177
31180
 
31181
+ /**
31182
+ * What a TRAVERSAL asks for: the crossing it retraces.
31183
+ *
31184
+ * The entry a push creates remembers the crossing that created it (see
31185
+ * recordCrossing). A back onto the page that crossing came from undoes it —
31186
+ * the same movement, the other way — and a forward onto an entry whose
31187
+ * crossing came from the page being left plays it again as it was. Both
31188
+ * entries are read because each side is the only one that knows its case: the
31189
+ * entry being LEFT says how it was reached (a back), the entry being REACHED
31190
+ * says how it was reached (a forward).
31191
+ *
31192
+ * Both can be true at once — A, B, A again: leaving the second A for B is the
31193
+ * way back of A → B and the way in of B → A. The entries' depths in the stack
31194
+ * tell them apart; without a depth on both, a back is assumed, the traversal
31195
+ * by far the most often made.
31196
+ *
31197
+ * Answers in the shape of readNavigationRequest, with every field said, so
31198
+ * that the relations have nothing left to answer for — and marked as a replay,
31199
+ * because one relation still outranks it: the one written for the exact way
31200
+ * travelled (see the watcher's onMove).
31201
+ */
31202
+ const readTraversalReplay = ({
31203
+ url,
31204
+ state
31205
+ }, {
31206
+ fromUrl,
31207
+ fromState
31208
+ }) => {
31209
+ const to = absoluteUrl(url);
31210
+ const from = absoluteUrl(fromUrl);
31211
+ if (!to || !from) {
31212
+ return null;
31213
+ }
31214
+ const crossingIn = crossingRecordedOn(state);
31215
+ const crossingOut = crossingRecordedOn(fromState);
31216
+ const isForward = crossingIn !== null && crossingIn.from === from;
31217
+ const isBack = crossingOut !== null && crossingOut.from === to;
31218
+ if (isForward && isBack) {
31219
+ const depthIn = navDepthOf(state);
31220
+ const depthOut = navDepthOf(fromState);
31221
+ if (depthIn !== undefined && depthOut !== undefined && depthIn > depthOut) {
31222
+ return replayOf(crossingIn);
31223
+ }
31224
+ return replayOf(reverseCrossing(crossingOut));
31225
+ }
31226
+ if (isForward) {
31227
+ return replayOf(crossingIn);
31228
+ }
31229
+ if (isBack) {
31230
+ return replayOf(reverseCrossing(crossingOut));
31231
+ }
31232
+ return null;
31233
+ };
31234
+ const crossingRecordedOn = state => {
31235
+ if (!state) {
31236
+ return null;
31237
+ }
31238
+ const crossing = state[CROSSING_STATE_KEY];
31239
+ if (!crossing || typeof crossing.from !== "string") {
31240
+ return null;
31241
+ }
31242
+ return crossing;
31243
+ };
31244
+ const navDepthOf = state => {
31245
+ if (state && typeof state[NAV_DEPTH_STATE_KEY] === "number") {
31246
+ return state[NAV_DEPTH_STATE_KEY];
31247
+ }
31248
+ return undefined;
31249
+ };
31250
+ const reverseCrossing = crossing => {
31251
+ return {
31252
+ ...crossing,
31253
+ direction: reverseDirection(crossing.direction)
31254
+ };
31255
+ };
31256
+
31257
+ // A direction that is neither ("" — a default has none) stays what it is.
31258
+ const reverseDirection = direction => {
31259
+ if (direction === "forward") {
31260
+ return "back";
31261
+ }
31262
+ if (direction === "back") {
31263
+ return "forward";
31264
+ }
31265
+ return direction;
31266
+ };
31267
+ const replayOf = ({
31268
+ type,
31269
+ direction,
31270
+ duration
31271
+ }) => {
31272
+ return {
31273
+ type,
31274
+ typeSaid: true,
31275
+ duration,
31276
+ direction,
31277
+ replay: true
31278
+ };
31279
+ };
31280
+
31178
31281
  // The request first, field by field, then what was defined for this pair (or
31179
31282
  // for everything). Written as one function because both ends of the file
31180
31283
  // resolve the same way: the one that knows the pair, and the one that only
@@ -31250,7 +31353,14 @@ const rebuildWatcher = () => {
31250
31353
  const fromPage = fromIndex === -1 ? null : pages[fromIndex];
31251
31354
  const toPage = index === -1 ? null : pages[index];
31252
31355
  const found = findRelation(fromPage, toPage);
31253
- if (!found && !navigationRequest) {
31356
+ // A traversal retraces its crossing over everything deduced here — the
31357
+ // reverse of a pair, a page from anywhere — and not over a relation
31358
+ // written for this exact way: that line is the author's one tool for
31359
+ // breaking reciprocity, and the back button is the way back it has to
31360
+ // reach. A request made by a link or a navTo() is never dropped: it is
31361
+ // about this one crossing, and a written relation is about every one.
31362
+ const request = navigationRequest && navigationRequest.replay && found && found.written ? null : navigationRequest;
31363
+ if (!found && !request) {
31254
31364
  // No relation says anything about these two and this navigation asked
31255
31365
  // for nothing: they are side by side, and silence is the fact — not a
31256
31366
  // missing case.
@@ -31259,12 +31369,15 @@ const rebuildWatcher = () => {
31259
31369
  const {
31260
31370
  type,
31261
31371
  duration
31262
- } = resolveTransition(navigationRequest, found ? found.relation : null);
31372
+ } = resolveTransition(request, found ? found.relation : null);
31263
31373
  if (type === "none") {
31264
31374
  // Silence said out loud: this way of the pair was written to play
31265
31375
  // nothing — or this one navigation asked for nothing — where the reverse
31266
31376
  // of the other way, or the default, would have played.
31267
31377
  navigationAnimated = true;
31378
+ navigationDecision = {
31379
+ type: "none"
31380
+ };
31268
31381
  return;
31269
31382
  }
31270
31383
  beginTransition({
@@ -31272,10 +31385,11 @@ const rebuildWatcher = () => {
31272
31385
  url: navigationUrl,
31273
31386
  fromUrl: navigationFromUrl,
31274
31387
  // Which way it plays: what the navigation itself said first — the link
31275
- // being pressed is where the way the app is being walked is known — then
31276
- // the relation, and forward for a navigation that asked for a movement
31277
- // between two pages no relation orders.
31278
- direction: navigationRequest && navigationRequest.direction || found && found.direction || "forward",
31388
+ // being pressed is where the way the app is being walked is known, and
31389
+ // a traversal says the way it retraces then the relation, and forward
31390
+ // for a navigation that asked for a movement between two pages no
31391
+ // relation orders.
31392
+ direction: request && request.direction !== undefined ? request.direction : found && found.direction || "forward",
31279
31393
  type,
31280
31394
  duration
31281
31395
  });
@@ -31304,6 +31418,13 @@ let navigationUrl = null;
31304
31418
  // the address has already moved and location would answer with the destination.
31305
31419
  let navigationFromUrl = null;
31306
31420
  let navigationAnimated = false;
31421
+ // "push", "replace", "traverse", … — a push is the one navigation that creates
31422
+ // the entry a crossing is recorded on.
31423
+ let navigationType = null;
31424
+ // What was decided for the navigation now landing — a movement, or "none" —
31425
+ // which is what its entry remembers (see recordCrossing). Null while nothing
31426
+ // has been decided, and for a navigation nothing was said about.
31427
+ let navigationDecision = null;
31307
31428
 
31308
31429
  // The two ends of every navigation, watched from here on. The picture of the
31309
31430
  // page being left has to be honest, so rendering is held from before the
@@ -31313,9 +31434,18 @@ let navigationAnimated = false;
31313
31434
  // one moment the DEFAULT can decide: every relation has had its say by then.
31314
31435
  observeBeforeRouting(details => {
31315
31436
  navigationAnimated = false;
31316
- navigationRequest = readNavigationRequest(details);
31437
+ navigationDecision = null;
31438
+ navigationType = details.navigationType;
31317
31439
  navigationUrl = details.url;
31318
31440
  navigationFromUrl = documentUrlSignal.peek();
31441
+ // A traversal has no element and no call to ask anything: what it asks is
31442
+ // the crossing it retraces — reversed for a back, as it was for a forward.
31443
+ // Read before the document state moves, so the state peeked is the entry
31444
+ // being left.
31445
+ navigationRequest = navigationType === "traverse" ? readTraversalReplay(details, {
31446
+ fromUrl: navigationFromUrl,
31447
+ fromState: documentStateSignal.peek()
31448
+ }) : readNavigationRequest(details);
31319
31449
  if (relations.length === 0 && !defaultTransition && !navigationRequest) {
31320
31450
  return;
31321
31451
  }
@@ -31332,17 +31462,23 @@ observeAfterRouting(() => {
31332
31462
  const request = navigationRequest;
31333
31463
  const url = navigationUrl;
31334
31464
  const fromUrl = navigationFromUrl;
31465
+ const type = navigationType;
31335
31466
  // Read here and dropped here: a request answers for the navigation it was
31336
31467
  // made on, and the next one is back to the relations.
31337
31468
  navigationRequest = null;
31338
31469
  navigationUrl = null;
31339
31470
  navigationFromUrl = null;
31471
+ navigationType = null;
31340
31472
  if (!navigationAnimated && (request || defaultTransition)) {
31341
31473
  const {
31342
31474
  type,
31343
31475
  duration
31344
31476
  } = resolveTransition(request, defaultTransition);
31345
- if (type !== "none") {
31477
+ if (type === "none") {
31478
+ navigationDecision = {
31479
+ type: "none"
31480
+ };
31481
+ } else {
31346
31482
  beginTransition({
31347
31483
  page: null,
31348
31484
  url,
@@ -31354,15 +31490,70 @@ observeAfterRouting(() => {
31354
31490
  // press that names the movement means forward unless it says
31355
31491
  // otherwise — and a movement of navi's is written on the direction,
31356
31492
  // so left empty it would play nothing at all.
31357
- direction: request && request.direction || (request && request.typeSaid ? "forward" : ""),
31493
+ direction: request && request.direction !== undefined ? request.direction : request && request.typeSaid ? "forward" : "",
31358
31494
  type,
31359
31495
  duration
31360
31496
  });
31361
31497
  }
31362
31498
  }
31499
+ if (type === "push") {
31500
+ recordCrossing({
31501
+ url,
31502
+ fromUrl,
31503
+ decision: navigationDecision
31504
+ });
31505
+ }
31506
+ navigationDecision = null;
31363
31507
  releaseRoutingRenderingHold();
31364
31508
  });
31365
31509
 
31510
+ // The entry a push created remembers what was decided on the way in, so that
31511
+ // the traversals leaving it or landing on it retrace it (see
31512
+ // readTraversalReplay). Written once the navigation has landed — the decision
31513
+ // needs the pages to be current, which is after the entry was created — as a
31514
+ // state-only replace: it announces nothing and routes nothing. Nothing is
31515
+ // written when nothing was decided: the silence between two unrelated pages is
31516
+ // not a crossing to remember. A replace keeps its entry's state, so an entry
31517
+ // reached by one keeps the crossing that led to where it stands.
31518
+ const recordCrossing = ({
31519
+ url,
31520
+ fromUrl,
31521
+ decision
31522
+ }) => {
31523
+ if (!decision) {
31524
+ return;
31525
+ }
31526
+ const to = absoluteUrl(url);
31527
+ const from = absoluteUrl(fromUrl);
31528
+ if (!to || !from) {
31529
+ return;
31530
+ }
31531
+ if (documentUrlSignal.peek() !== to) {
31532
+ // Superseded before it landed: the entry now current is another one's.
31533
+ return;
31534
+ }
31535
+ const crossing = {
31536
+ from
31537
+ };
31538
+ if (decision.type !== undefined) {
31539
+ crossing.type = decision.type;
31540
+ }
31541
+ if (decision.direction !== undefined) {
31542
+ crossing.direction = decision.direction;
31543
+ }
31544
+ if (decision.duration !== undefined) {
31545
+ crossing.duration = decision.duration;
31546
+ }
31547
+ const state = documentStateSignal.peek();
31548
+ navTo(to, {
31549
+ replace: true,
31550
+ state: {
31551
+ ...(state || {}),
31552
+ [CROSSING_STATE_KEY]: crossing
31553
+ }
31554
+ });
31555
+ };
31556
+
31366
31557
  // The exact way travelled first, over the whole registry, then the reverses,
31367
31558
  // and last the pages written from anywhere.
31368
31559
  //
@@ -31375,6 +31566,11 @@ observeAfterRouting(() => {
31375
31566
  // same destination still owns its crossing — the map, where it was drawn, is
31376
31567
  // more precise than "from wherever". Arriving is read before leaving: between
31377
31568
  // two such pages, the one being opened says what plays.
31569
+ //
31570
+ // Only the first answer is `written`: a sentence the author wrote about this
31571
+ // exact way. Every other answer is deduced from a sentence about something
31572
+ // else, and a traversal retracing its own crossing knows better than a
31573
+ // deduction (see readTraversalReplay).
31378
31574
  const findRelation = (fromPage, toPage) => {
31379
31575
  for (const relation of relations) {
31380
31576
  if (!relation.from) {
@@ -31383,7 +31579,8 @@ const findRelation = (fromPage, toPage) => {
31383
31579
  if (samePage$1(relation.from, fromPage) && samePage$1(relation.to, toPage)) {
31384
31580
  return {
31385
31581
  direction: "forward",
31386
- relation
31582
+ relation,
31583
+ written: true
31387
31584
  };
31388
31585
  }
31389
31586
  }
@@ -31445,12 +31642,19 @@ const beginTransition = ({
31445
31642
  console.warn("A RouteTravel is animating this navigation; the route transition defined between these routes is skipped. Animate a pair of routes with RouteTravel or defineRouteTransition, not both.");
31446
31643
  return;
31447
31644
  }
31645
+ navigationDecision = {
31646
+ type,
31647
+ direction,
31648
+ duration
31649
+ };
31448
31650
  // The two ends of the crossing, kept for the length of the movement: they are
31449
31651
  // what lets the navigation after this one be recognised as its way back (see
31450
- // turnRunningTransitionRound).
31652
+ // turnRunningTransitionRound) — and what it decided, which that way back
31653
+ // then undoes.
31451
31654
  const transition = {
31452
31655
  fromUrl: absoluteUrl(fromUrl),
31453
31656
  url: absoluteUrl(url),
31657
+ decision: navigationDecision,
31454
31658
  walkHome: null,
31455
31659
  releaseReverting: null
31456
31660
  };
@@ -31626,6 +31830,7 @@ const turnRunningTransitionRound = (fromUrl, url) => {
31626
31830
  }
31627
31831
  const animations = viewTransitionAnimations();
31628
31832
  if (isWayInAgain) {
31833
+ navigationDecision = running.decision;
31629
31834
  // The token is dropped first: the walk it stands for is the one that must
31630
31835
  // not arrive anywhere anymore, and its promise is still pending.
31631
31836
  running.walkHome = null;
@@ -31643,6 +31848,9 @@ const turnRunningTransitionRound = (fromUrl, url) => {
31643
31848
  // played, and nothing was on screen to teleport.
31644
31849
  return false;
31645
31850
  }
31851
+ // What this navigation plays, for the entry it may create: the movement on
31852
+ // screen, the other way.
31853
+ navigationDecision = reverseCrossing(running.decision);
31646
31854
  // Which walk home this is, so the one that arrives is the one still wanted: a
31647
31855
  // walk turned round mid-way leaves a promise nobody cancelled, and it settles
31648
31856
  // when the pictures reach the far end.
@@ -31845,7 +32053,7 @@ const warnAboutBothWaysWritten = ({
31845
32053
  // wrote them in, and the order it will find them in to fix them.
31846
32054
  const written = `${describePage(reverse.from)} → ${describePage(reverse.to)}`;
31847
32055
  const added = `${describePage(from)} → ${describePage(to)}`;
31848
- warnOnce(`both-ways-written:${written}|${added}`, `${written} and ${added} are both written with the same movement, so BOTH crossings play forward and this pair can never say "back" — the back button included. A relation written for the exact way travelled wins over being the reverse of another (see findRelation), which is what makes reciprocity the default: write the way back only to give it a DIFFERENT movement, or "none" to silence it. A single crossing that walks the map backwards says so on itself instead: <Link routeTransition={{ direction: "forward" }}>, or navTo(url, { routeTransition: { direction: "forward" } }).`);
32056
+ warnOnce(`both-ways-written:${written}|${added}`, `${written} and ${added} are both written with the same movement, so BOTH crossings play forward and this pair can never say "back" — the back button included. A relation written for the exact way travelled wins over being the reverse of another, and over the crossing a history traversal retraces (see findRelation), which is what makes reciprocity the default: write the way back only to give it a DIFFERENT movement, or "none" to silence it. A single crossing that walks the map backwards says so on itself instead: <Link routeTransition={{ direction: "forward" }}>, or navTo(url, { routeTransition: { direction: "forward" } }).`);
31849
32057
  };
31850
32058
  const warnPagesBothCurrent = (pageKept, pageIgnored) => {
31851
32059
  const kept = describePage(pageKept);