@jsenv/navi 0.29.502 → 0.29.503

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.
@@ -36863,6 +36863,14 @@ const collectBranches = (children, path = "") => {
36863
36863
  const paramsMatching = routeParams ? route.matchesParams(routeParams) : true;
36864
36864
  if (!matchingBranch && matching && paramsMatching) {
36865
36865
  matchingBranch = branch;
36866
+ if (!routeParams) {
36867
+ // The params are part of the page this branch shows: /games/a and
36868
+ // /games/b are two pages on one branch, and the container renders
36869
+ // between them so the second is announced like any other (see
36870
+ // publishRouteRender) — a movement between the two waits on it.
36871
+ // eslint-disable-next-line no-unused-expressions
36872
+ route.paramsSignal.value;
36873
+ }
36866
36874
  }
36867
36875
  }
36868
36876
  };
@@ -36927,6 +36935,59 @@ const RouteUI = ({
36927
36935
  return element;
36928
36936
  };
36929
36937
 
36938
+ /*
36939
+ * What ONE navigation asks of a route transition, worn by the element being
36940
+ * pressed: `<Link routeTransition>`, `<Button routeTransition>`, or the
36941
+ * attribute by hand on any anchor. A type travels as its name — all most
36942
+ * presses have to say — and anything more (a way round, a pace) as the same
36943
+ * object navTo() takes, written as JSON.
36944
+ *
36945
+ * Apart from route_transition.jsx, which plays the request: the press handlers
36946
+ * and the --navi-nav-to command read it too, and importing the transition
36947
+ * machinery there would pull it into an application that defines no
36948
+ * transition.
36949
+ */
36950
+
36951
+ const ROUTE_TRANSITION_REQUEST_ATTRIBUTE = "data-navi-route-transition-request";
36952
+
36953
+ // The attributes to spread on the element. Nothing when nothing is asked, so
36954
+ // that the attribute written by hand on the same element is left standing.
36955
+ const writeRouteTransitionRequest = (routeTransition) => {
36956
+ if (routeTransition === undefined || routeTransition === null) {
36957
+ return null;
36958
+ }
36959
+ return {
36960
+ [ROUTE_TRANSITION_REQUEST_ATTRIBUTE]:
36961
+ typeof routeTransition === "string"
36962
+ ? routeTransition
36963
+ : JSON.stringify(routeTransition),
36964
+ };
36965
+ };
36966
+
36967
+ // The request as navTo() takes it — a type name or the object — and undefined
36968
+ // when the element asks nothing.
36969
+ const readRouteTransitionRequest = (element) => {
36970
+ const asked = element.getAttribute(ROUTE_TRANSITION_REQUEST_ATTRIBUTE);
36971
+ if (asked === null) {
36972
+ return undefined;
36973
+ }
36974
+ const value = asked.trim();
36975
+ if (value === "") {
36976
+ return undefined;
36977
+ }
36978
+ if (value[0] !== "{") {
36979
+ return value;
36980
+ }
36981
+ try {
36982
+ return JSON.parse(value);
36983
+ } catch {
36984
+ console.warn(
36985
+ `${ROUTE_TRANSITION_REQUEST_ATTRIBUTE} is neither a type name nor JSON: ${value}`,
36986
+ );
36987
+ return undefined;
36988
+ }
36989
+ };
36990
+
36930
36991
  /**
36931
36992
  * Where a route movement is heading, said before its first picture is taken.
36932
36993
  *
@@ -38338,10 +38399,6 @@ const TRANSITION_COVERED_ATTRIBUTE = "data-navi-route-transition-covered";
38338
38399
  // right for a page that IS the whole viewport.
38339
38400
  const TRANSITION_AREA_ATTRIBUTE = "data-navi-route-transition-area";
38340
38401
  const TRANSITION_TARGET_ATTRIBUTE = "data-navi-route-transition-target";
38341
- // What ONE navigation asks for, over whatever the relations say: worn by the
38342
- // link being pressed (see <Link routeTransition>), or handed to navTo(). It answers
38343
- // for that navigation and for no other — the next one is back to the relations.
38344
- const TRANSITION_REQUEST_ATTRIBUTE = "data-navi-route-transition-request";
38345
38402
  // What a history entry remembers of the crossing that created it, in the
38346
38403
  // entry's own state: `{ from, type, direction, duration }` (see recordCrossing).
38347
38404
  const CROSSING_STATE_KEY = "jsenv_route_transition";
@@ -38882,13 +38939,32 @@ const defineRouteTransition = (from, to, transition) => {
38882
38939
  * "before" the other — so give it a movement that does not need one:
38883
38940
  * `"cross-fade"`, or a custom type whose CSS is keyed on the type alone.
38884
38941
  *
38885
- * @param {string|{type?: string, duration?: number|string}} transition - same
38886
- * forms as defineRouteTransition's. `"none"` (or removing the default) puts
38887
- * the silence back.
38942
+ * Unless the history says it: `{ type: "slide-x", direction: "history" }` is
38943
+ * the grammar of a navigation stack, where every link goes one level deeper
38944
+ * and every back one level up. A push plays forward, a traversal retraces its
38945
+ * crossing or, with none remembered, plays back when it lands lower in the
38946
+ * history and forward when it lands higher, and a replace — the same level —
38947
+ * plays nothing. It plays only when the page changes: a push that opens a
38948
+ * layer over the page keeps the page. The relations written still answer
38949
+ * first, and a page written from anywhere then answers for its way out only
38950
+ * on a back — a link out of it is a push like any other (see findRelation).
38951
+ *
38952
+ * @param {string|{type?: string, duration?: number|string, direction?: "history"}} transition -
38953
+ * same forms as defineRouteTransition's. `"none"` (or removing the default)
38954
+ * puts the silence back.
38888
38955
  * @returns {() => void} remove this default.
38889
38956
  */
38890
38957
  const defineRouteDefaultTransition = transition => {
38891
- const value = normalizeTransition(transition);
38958
+ const {
38959
+ direction
38960
+ } = typeof transition === "object" && transition !== null ? transition : {};
38961
+ if (direction !== undefined && direction !== "history") {
38962
+ throw new TypeError(`defineRouteDefaultTransition takes direction: "history" or no direction, not ${JSON.stringify(direction)}: a default is about pages no relation orders, and "history" is the one thing that orders them — the depth of the entries the navigation goes between.`);
38963
+ }
38964
+ const value = {
38965
+ ...normalizeTransition(transition),
38966
+ readsHistory: direction === "history"
38967
+ };
38892
38968
  defaultTransition = value;
38893
38969
  installRouteTransitionCss();
38894
38970
  return () => {
@@ -38941,29 +39017,8 @@ const readNavigationRequest = ({
38941
39017
  return normalizeRequest(routeTransition);
38942
39018
  }
38943
39019
  if (element && element.getAttribute) {
38944
- const asked = element.getAttribute(TRANSITION_REQUEST_ATTRIBUTE);
38945
- if (asked === null) {
38946
- return null;
38947
- }
38948
- const value = asked.trim();
38949
- if (value === "") {
38950
- return null;
38951
- }
38952
- // A type is a name, and a name is all most links have to say. Anything
38953
- // more — a way round, a pace — is the same object the API takes
38954
- // everywhere else, written as JSON so that it travels on an attribute
38955
- // (and so that a plain <a> can say it too).
38956
- if (value[0] === "{") {
38957
- let parsed;
38958
- try {
38959
- parsed = JSON.parse(value);
38960
- } catch {
38961
- console.warn(`${TRANSITION_REQUEST_ATTRIBUTE} is neither a type name nor JSON: ${value}`);
38962
- return null;
38963
- }
38964
- return normalizeRequest(parsed);
38965
- }
38966
- return normalizeRequest(value);
39020
+ const asked = readRouteTransitionRequest(element);
39021
+ return asked === undefined ? null : normalizeRequest(asked);
38967
39022
  }
38968
39023
  return null;
38969
39024
  };
@@ -39049,6 +39104,31 @@ const crossingRecordedOn = state => {
39049
39104
  }
39050
39105
  return crossing;
39051
39106
  };
39107
+
39108
+ /**
39109
+ * Which way the history says a navigation goes, for a default that reads it
39110
+ * (see defineRouteDefaultTransition): a push is one level deeper, a traversal
39111
+ * goes where the depth of the entry it lands on puts it — back when that is
39112
+ * not known, as in readTraversalReplay —, and anything else (a replace) stays
39113
+ * on its level: null.
39114
+ */
39115
+ const readHistoryDirection = (navigationType, {
39116
+ state,
39117
+ fromState
39118
+ }) => {
39119
+ if (navigationType === "push") {
39120
+ return "forward";
39121
+ }
39122
+ if (navigationType !== "traverse") {
39123
+ return null;
39124
+ }
39125
+ const depthIn = navDepthOf(state);
39126
+ const depthOut = navDepthOf(fromState);
39127
+ if (depthIn !== undefined && depthOut !== undefined && depthIn > depthOut) {
39128
+ return "forward";
39129
+ }
39130
+ return "back";
39131
+ };
39052
39132
  const navDepthOf = state => {
39053
39133
  if (state && typeof state[NAV_DEPTH_STATE_KEY] === "number") {
39054
39134
  return state[NAV_DEPTH_STATE_KEY];
@@ -39232,6 +39312,10 @@ let navigationAnimated = false;
39232
39312
  // "push", "replace", "traverse", … — a push is the one navigation that creates
39233
39313
  // the entry a crossing is recorded on.
39234
39314
  let navigationType = null;
39315
+ // Which way the history says this navigation goes — "forward", "back", or null
39316
+ // for a replace — when the default reads it (see readHistoryDirection), and
39317
+ // undefined when nothing does.
39318
+ let navigationHistoryDirection;
39235
39319
  // What was decided for the navigation now landing — a movement, or "none" —
39236
39320
  // which is what its entry remembers (see recordCrossing). Null while nothing
39237
39321
  // has been decided, and for a navigation nothing was said about.
@@ -39249,14 +39333,19 @@ observeBeforeRouting(details => {
39249
39333
  navigationType = details.navigationType;
39250
39334
  navigationUrl = details.url;
39251
39335
  navigationFromUrl = documentUrlSignal.peek();
39252
- // A traversal has no element and no call to ask anything: what it asks is
39253
- // the crossing it retraces — reversed for a back, as it was for a forward.
39254
39336
  // Read before the document state moves, so the state peeked is the entry
39255
39337
  // being left.
39338
+ const fromState = documentStateSignal.peek();
39339
+ // A traversal has no element and no call to ask anything: what it asks is
39340
+ // the crossing it retraces — reversed for a back, as it was for a forward.
39256
39341
  navigationRequest = navigationType === "traverse" ? readTraversalReplay(details, {
39257
39342
  fromUrl: navigationFromUrl,
39258
- fromState: documentStateSignal.peek()
39343
+ fromState
39259
39344
  }) : readNavigationRequest(details);
39345
+ navigationHistoryDirection = defaultTransition && defaultTransition.readsHistory ? readHistoryDirection(navigationType, {
39346
+ state: details.state,
39347
+ fromState
39348
+ }) : undefined;
39260
39349
  if (relations.length === 0 && !defaultTransition && !navigationRequest) {
39261
39350
  return;
39262
39351
  }
@@ -39274,17 +39363,24 @@ observeAfterRouting(() => {
39274
39363
  const url = navigationUrl;
39275
39364
  const fromUrl = navigationFromUrl;
39276
39365
  const type = navigationType;
39366
+ const historyDirection = navigationHistoryDirection;
39277
39367
  // Read here and dropped here: a request answers for the navigation it was
39278
39368
  // made on, and the next one is back to the relations.
39279
39369
  navigationRequest = null;
39280
39370
  navigationUrl = null;
39281
39371
  navigationFromUrl = null;
39282
39372
  navigationType = null;
39283
- if (!navigationAnimated && (request || defaultTransition)) {
39373
+ navigationHistoryDirection = undefined;
39374
+ // A default reading the history plays between two pages a level apart: not
39375
+ // on a replace, and not on a navigation that keeps the page — a push
39376
+ // opening a layer over it moves the layer, not the page (see
39377
+ // page_change.js).
39378
+ const base = defaultTransition && (!defaultTransition.readsHistory || historyDirection !== null && isRenderingPageChange()) ? defaultTransition : null;
39379
+ if (!navigationAnimated && (request || base)) {
39284
39380
  const {
39285
39381
  type,
39286
39382
  duration
39287
- } = resolveTransition(request, defaultTransition);
39383
+ } = resolveTransition(request, base);
39288
39384
  if (type === "none") {
39289
39385
  navigationDecision = {
39290
39386
  type: "none"
@@ -39297,11 +39393,12 @@ observeAfterRouting(() => {
39297
39393
  // A default has no direction: nothing says which of two arbitrary
39298
39394
  // pages is before the other, and the attribute is then worn empty —
39299
39395
  // present for whoever keys on "one of ours is playing", silent on the
39300
- // way. A request is the other case: a navigation IS a way round, so a
39301
- // press that names the movement means forward unless it says
39302
- // otherwise — and a movement of navi's is written on the direction,
39303
- // so left empty it would play nothing at all.
39304
- direction: request && request.direction !== undefined ? request.direction : request && request.typeSaid ? "forward" : "",
39396
+ // way — unless it reads the history. A request is the other case: a
39397
+ // navigation IS a way round, so a press that names the movement means
39398
+ // forward unless it says otherwise — and a movement of navi's is
39399
+ // written on the direction, so left empty it would play nothing at
39400
+ // all.
39401
+ direction: request && request.direction !== undefined ? request.direction : request && request.typeSaid ? "forward" : base && base.readsHistory ? historyDirection : "",
39305
39402
  type,
39306
39403
  duration
39307
39404
  });
@@ -39417,6 +39514,12 @@ const findRelation = (fromPage, toPage) => {
39417
39514
  };
39418
39515
  }
39419
39516
  }
39517
+ // Leaving such a page plays back because leaving it is assumed to be
39518
+ // closing it. With the default reading the history, that is known instead:
39519
+ // a link out of it is a push, one level deeper, and the default answers.
39520
+ if (navigationHistoryDirection !== undefined && navigationHistoryDirection !== "back") {
39521
+ return null;
39522
+ }
39420
39523
  for (const relation of relations) {
39421
39524
  if (relation.from) {
39422
39525
  continue;
@@ -40330,7 +40433,11 @@ const RouteTravel = ({
40330
40433
  // a travel is between two of them, and by the time anything renders the first
40331
40434
  // one is already gone. Written after each render (below), so a subscriber
40332
40435
  // reading it — they all run before Preact flushes — reads the one being left.
40333
- const currentIndexRef = useRef(currentIndex);
40436
+ // The page and not its place in the row: a row given again around the page
40437
+ // on screen (a window over a list, read anew around it) puts that page at
40438
+ // another index without anything having moved.
40439
+ const currentPage = currentIndex === -1 ? null : pages[currentIndex];
40440
+ const currentPageRef = useRef(currentPage);
40334
40441
 
40335
40442
  // Where a page is asked for, whoever asks: a page is a route AND the params
40336
40443
  // that say which of its tabs, and a caller told only the route would send the
@@ -40526,11 +40633,13 @@ const RouteTravel = ({
40526
40633
  if (index === -1) {
40527
40634
  return;
40528
40635
  }
40529
- if (index === currentIndexRef.current) {
40530
- // Where the row already was — the first reading of all, and any move
40531
- // this box has already taken note of (a render writes it too). What it
40532
- // was still waiting for is here nonetheless, so the wait is called off.
40533
- if (samePage(pageAskedForRef.current, pages[index])) {
40636
+ const page = pages[index];
40637
+ if (samePage(page, currentPageRef.current)) {
40638
+ // Where the row already was — the first reading of all, a row given
40639
+ // again around the page on screen, and any move this box has already
40640
+ // taken note of (a render writes it too). What it was still waiting
40641
+ // for is here nonetheless, so the wait is called off.
40642
+ if (samePage(pageAskedForRef.current, page)) {
40534
40643
  pageAskedForRef.current = null;
40535
40644
  }
40536
40645
  return;
@@ -40539,13 +40648,12 @@ const RouteTravel = ({
40539
40648
  // between two of them is a page change the path does not show (see
40540
40649
  // page_change.js).
40541
40650
  notePageChange();
40542
- const page = pages[index];
40543
40651
  // A page this box asked for itself — a travel's own navigation, or one
40544
40652
  // it had given up waiting on: what arrives here is the answer to a
40545
40653
  // question already answered, not somebody going somewhere.
40546
40654
  if (samePage(pageAskedForRef.current, page)) {
40547
40655
  pageAskedForRef.current = null;
40548
- currentIndexRef.current = index;
40656
+ currentPageRef.current = page;
40549
40657
  return;
40550
40658
  }
40551
40659
  // Somewhere else arrived first: whatever this box was still waiting for
@@ -40557,15 +40665,15 @@ const RouteTravel = ({
40557
40665
  // flight is the answer, aimed somewhere else. Starting a second one on
40558
40666
  // top would leave this one's pictures to be dropped mid-slide.
40559
40667
  if (travelRef.current) {
40560
- currentIndexRef.current = index;
40668
+ currentPageRef.current = page;
40561
40669
  retargetTravel(travelRef.current, page);
40562
40670
  return;
40563
40671
  }
40564
- const fromIndex = currentIndexRef.current;
40565
- currentIndexRef.current = index;
40566
- if (fromIndex === -1 || fromIndex === index) {
40567
- // Arriving from outside this row (or not moving at all): there is no
40568
- // pair of pages to show, so there is nothing to travel between.
40672
+ const fromIndex = pageIndexOf(pages, currentPageRef.current);
40673
+ currentPageRef.current = page;
40674
+ if (fromIndex === -1) {
40675
+ // Arriving from outside this row: there is no pair of pages to show,
40676
+ // so there is nothing to travel between.
40569
40677
  return;
40570
40678
  }
40571
40679
  beginTravel({
@@ -40600,8 +40708,8 @@ const RouteTravel = ({
40600
40708
  // What the next announcement will compare itself against: written after the
40601
40709
  // render that shows it, so it is always the page one is looking at.
40602
40710
  useLayoutEffect(() => {
40603
- currentIndexRef.current = currentIndex;
40604
- }, [currentIndex]);
40711
+ currentPageRef.current = currentPage;
40712
+ }, [currentPage]);
40605
40713
 
40606
40714
  // Let go of far enough: the movement carries on from under the finger, at its
40607
40715
  // own pace, to the end.
@@ -43563,13 +43671,15 @@ const navToCommand = (source, event, { argument }) => {
43563
43671
  return {
43564
43672
  target,
43565
43673
  // Which way there is worn by the source, as a link wears it (see
43566
- // link_replace.js, link_return.js, link_open.js):
43674
+ // link_replace.js, link_return.js, link_open.js,
43675
+ // route_transition_request.js):
43567
43676
  // `<Button command="--navi-nav-to:/done" replace>`.
43568
43677
  implementation: () =>
43569
43678
  navTo(argument, {
43570
43679
  replace: linkAsksForReplace(source),
43571
43680
  asReturn: linkAsksForReturn(source),
43572
43681
  open: linkAsksToOpen(source),
43682
+ routeTransition: readRouteTransitionRequest(source),
43573
43683
  }),
43574
43684
  };
43575
43685
  };
@@ -56466,10 +56576,8 @@ const LinkPlain = props => {
56466
56576
 
56467
56577
  // What this link asks of a route transition, worn as an attribute so that
56468
56578
  // the navigation reads it off the element being pressed (see
56469
- // route_transition.jsx, which owns the name and does the reading). A type is
56470
- // a name; anything more travels as JSON, which is also how a plain <a>
56471
- // writes it by hand.
56472
- const routeTransitionRequest = routeTransition === undefined || routeTransition === null ? undefined : typeof routeTransition === "string" ? routeTransition : JSON.stringify(routeTransition);
56579
+ // route_transition_request.js, which owns the name and does the reading).
56580
+ const routeTransitionRequest = writeRouteTransitionRequest(routeTransition);
56473
56581
 
56474
56582
  // Which way this link goes to the place it aims at, worn as an attribute so
56475
56583
  // that whoever answers the press reads it off the anchor (see
@@ -56560,7 +56668,7 @@ const LinkPlain = props => {
56560
56668
  open: undefined,
56561
56669
  document: undefined,
56562
56670
  prefetch: undefined,
56563
- "data-navi-route-transition-request": routeTransitionRequest,
56671
+ ...routeTransitionRequest,
56564
56672
  ...(pressableDuringRouteTransition ? {
56565
56673
  [PRESSABLE_ATTRIBUTE]: ""
56566
56674
  } : null),
@@ -61270,6 +61378,7 @@ const ButtonUI = props => {
61270
61378
  asReturn,
61271
61379
  open,
61272
61380
  document: isDocument,
61381
+ routeTransition,
61273
61382
  pressableDuringRouteTransition,
61274
61383
  prefetch = true,
61275
61384
  // visual
@@ -61327,6 +61436,11 @@ const ButtonUI = props => {
61327
61436
  [LINK_DOCUMENT_ATTRIBUTE]: ""
61328
61437
  } : null;
61329
61438
 
61439
+ // What this press asks of a route transition (see
61440
+ // route_transition_request.js): read off the anchor by the navigation, off
61441
+ // the source by --navi-nav-to.
61442
+ const routeTransitionRequest = writeRouteTransitionRequest(routeTransition);
61443
+
61330
61444
  // Worn as an attribute too, and read at the document by whoever catches the
61331
61445
  // press a movement would have swallowed (see transition_press.js).
61332
61446
  const pressableRequest = pressableDuringRouteTransition ? {
@@ -61383,6 +61497,8 @@ const ButtonUI = props => {
61383
61497
  ...openRequest,
61384
61498
  document: undefined,
61385
61499
  ...documentRequest,
61500
+ routeTransition: undefined,
61501
+ ...routeTransitionRequest,
61386
61502
  pressableDuringRouteTransition: undefined,
61387
61503
  ...pressableRequest,
61388
61504
  prefetch: undefined
@@ -62039,6 +62155,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
62039
62155
  * replace?: boolean,
62040
62156
  * asReturn?: boolean,
62041
62157
  * open?: string | { id: string, type?: "push" | "replace" },
62158
+ * routeTransition?: string | { type?: string, duration?: number | string, direction?: "forward" | "back" },
62042
62159
  * document?: boolean,
62043
62160
  * actionStandalone?: boolean,
62044
62161
  * actionAbortable?: boolean,
@@ -62087,6 +62204,11 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
62087
62204
  * @param {string|{id: string, type?: "push"|"replace"}} [open] Lands with one
62088
62205
  * of the destination's popups open, named the way its `navState` names it:
62089
62206
  * what `<Link open>` says, for a press drawn as a button.
62207
+ * @param {string|{type?: string, duration?: number|string, direction?: "forward"|"back"}} [routeTransition]
62208
+ * What the press asks of a route transition, for that one navigation — a
62209
+ * type name, or an object to also say the pace or which way it plays, over
62210
+ * what the pair's relation says: what `<Link routeTransition>` says, read
62211
+ * off an `href`, a `route` or a `--navi-nav-to` command.
62090
62212
  * @param {boolean} [document] The `href` is ANOTHER DOCUMENT of this origin —
62091
62213
  * an admin panel, a status page, anything built as its own entry point — so
62092
62214
  * the press is left to the browser and loads that page, instead of being