@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.
@@ -38114,6 +38114,14 @@ const collectBranches = (children, path = "") => {
38114
38114
  const paramsMatching = routeParams ? route.matchesParams(routeParams) : true;
38115
38115
  if (!matchingBranch && matching && paramsMatching) {
38116
38116
  matchingBranch = branch;
38117
+ if (!routeParams) {
38118
+ // The params are part of the page this branch shows: /games/a and
38119
+ // /games/b are two pages on one branch, and the container renders
38120
+ // between them so the second is announced like any other (see
38121
+ // publishRouteRender) — a movement between the two waits on it.
38122
+ // eslint-disable-next-line no-unused-expressions
38123
+ route.paramsSignal.value;
38124
+ }
38117
38125
  }
38118
38126
  }
38119
38127
  };
@@ -38178,6 +38186,59 @@ const RouteUI = ({
38178
38186
  return element;
38179
38187
  };
38180
38188
 
38189
+ /*
38190
+ * What ONE navigation asks of a route transition, worn by the element being
38191
+ * pressed: `<Link routeTransition>`, `<Button routeTransition>`, or the
38192
+ * attribute by hand on any anchor. A type travels as its name — all most
38193
+ * presses have to say — and anything more (a way round, a pace) as the same
38194
+ * object navTo() takes, written as JSON.
38195
+ *
38196
+ * Apart from route_transition.jsx, which plays the request: the press handlers
38197
+ * and the --navi-nav-to command read it too, and importing the transition
38198
+ * machinery there would pull it into an application that defines no
38199
+ * transition.
38200
+ */
38201
+
38202
+ const ROUTE_TRANSITION_REQUEST_ATTRIBUTE = "data-navi-route-transition-request";
38203
+
38204
+ // The attributes to spread on the element. Nothing when nothing is asked, so
38205
+ // that the attribute written by hand on the same element is left standing.
38206
+ const writeRouteTransitionRequest = (routeTransition) => {
38207
+ if (routeTransition === undefined || routeTransition === null) {
38208
+ return null;
38209
+ }
38210
+ return {
38211
+ [ROUTE_TRANSITION_REQUEST_ATTRIBUTE]:
38212
+ typeof routeTransition === "string"
38213
+ ? routeTransition
38214
+ : JSON.stringify(routeTransition),
38215
+ };
38216
+ };
38217
+
38218
+ // The request as navTo() takes it — a type name or the object — and undefined
38219
+ // when the element asks nothing.
38220
+ const readRouteTransitionRequest = (element) => {
38221
+ const asked = element.getAttribute(ROUTE_TRANSITION_REQUEST_ATTRIBUTE);
38222
+ if (asked === null) {
38223
+ return undefined;
38224
+ }
38225
+ const value = asked.trim();
38226
+ if (value === "") {
38227
+ return undefined;
38228
+ }
38229
+ if (value[0] !== "{") {
38230
+ return value;
38231
+ }
38232
+ try {
38233
+ return JSON.parse(value);
38234
+ } catch {
38235
+ console.warn(
38236
+ `${ROUTE_TRANSITION_REQUEST_ATTRIBUTE} is neither a type name nor JSON: ${value}`,
38237
+ );
38238
+ return undefined;
38239
+ }
38240
+ };
38241
+
38181
38242
  /**
38182
38243
  * Where a route movement is heading, said before its first picture is taken.
38183
38244
  *
@@ -39629,10 +39690,6 @@ const TRANSITION_COVERED_ATTRIBUTE = "data-navi-route-transition-covered";
39629
39690
  // right for a page that IS the whole viewport.
39630
39691
  const TRANSITION_AREA_ATTRIBUTE = "data-navi-route-transition-area";
39631
39692
  const TRANSITION_TARGET_ATTRIBUTE = "data-navi-route-transition-target";
39632
- // What ONE navigation asks for, over whatever the relations say: worn by the
39633
- // link being pressed (see <Link routeTransition>), or handed to navTo(). It answers
39634
- // for that navigation and for no other — the next one is back to the relations.
39635
- const TRANSITION_REQUEST_ATTRIBUTE = "data-navi-route-transition-request";
39636
39693
  // What a history entry remembers of the crossing that created it, in the
39637
39694
  // entry's own state: `{ from, type, direction, duration }` (see recordCrossing).
39638
39695
  const CROSSING_STATE_KEY = "jsenv_route_transition";
@@ -40173,13 +40230,32 @@ const defineRouteTransition = (from, to, transition) => {
40173
40230
  * "before" the other — so give it a movement that does not need one:
40174
40231
  * `"cross-fade"`, or a custom type whose CSS is keyed on the type alone.
40175
40232
  *
40176
- * @param {string|{type?: string, duration?: number|string}} transition - same
40177
- * forms as defineRouteTransition's. `"none"` (or removing the default) puts
40178
- * the silence back.
40233
+ * Unless the history says it: `{ type: "slide-x", direction: "history" }` is
40234
+ * the grammar of a navigation stack, where every link goes one level deeper
40235
+ * and every back one level up. A push plays forward, a traversal retraces its
40236
+ * crossing or, with none remembered, plays back when it lands lower in the
40237
+ * history and forward when it lands higher, and a replace — the same level —
40238
+ * plays nothing. It plays only when the page changes: a push that opens a
40239
+ * layer over the page keeps the page. The relations written still answer
40240
+ * first, and a page written from anywhere then answers for its way out only
40241
+ * on a back — a link out of it is a push like any other (see findRelation).
40242
+ *
40243
+ * @param {string|{type?: string, duration?: number|string, direction?: "history"}} transition -
40244
+ * same forms as defineRouteTransition's. `"none"` (or removing the default)
40245
+ * puts the silence back.
40179
40246
  * @returns {() => void} remove this default.
40180
40247
  */
40181
40248
  const defineRouteDefaultTransition = transition => {
40182
- const value = normalizeTransition(transition);
40249
+ const {
40250
+ direction
40251
+ } = typeof transition === "object" && transition !== null ? transition : {};
40252
+ if (direction !== undefined && direction !== "history") {
40253
+ 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.`);
40254
+ }
40255
+ const value = {
40256
+ ...normalizeTransition(transition),
40257
+ readsHistory: direction === "history"
40258
+ };
40183
40259
  defaultTransition = value;
40184
40260
  installRouteTransitionCss();
40185
40261
  return () => {
@@ -40232,29 +40308,8 @@ const readNavigationRequest = ({
40232
40308
  return normalizeRequest(routeTransition);
40233
40309
  }
40234
40310
  if (element && element.getAttribute) {
40235
- const asked = element.getAttribute(TRANSITION_REQUEST_ATTRIBUTE);
40236
- if (asked === null) {
40237
- return null;
40238
- }
40239
- const value = asked.trim();
40240
- if (value === "") {
40241
- return null;
40242
- }
40243
- // A type is a name, and a name is all most links have to say. Anything
40244
- // more — a way round, a pace — is the same object the API takes
40245
- // everywhere else, written as JSON so that it travels on an attribute
40246
- // (and so that a plain <a> can say it too).
40247
- if (value[0] === "{") {
40248
- let parsed;
40249
- try {
40250
- parsed = JSON.parse(value);
40251
- } catch {
40252
- console.warn(`${TRANSITION_REQUEST_ATTRIBUTE} is neither a type name nor JSON: ${value}`);
40253
- return null;
40254
- }
40255
- return normalizeRequest(parsed);
40256
- }
40257
- return normalizeRequest(value);
40311
+ const asked = readRouteTransitionRequest(element);
40312
+ return asked === undefined ? null : normalizeRequest(asked);
40258
40313
  }
40259
40314
  return null;
40260
40315
  };
@@ -40340,6 +40395,31 @@ const crossingRecordedOn = state => {
40340
40395
  }
40341
40396
  return crossing;
40342
40397
  };
40398
+
40399
+ /**
40400
+ * Which way the history says a navigation goes, for a default that reads it
40401
+ * (see defineRouteDefaultTransition): a push is one level deeper, a traversal
40402
+ * goes where the depth of the entry it lands on puts it — back when that is
40403
+ * not known, as in readTraversalReplay —, and anything else (a replace) stays
40404
+ * on its level: null.
40405
+ */
40406
+ const readHistoryDirection = (navigationType, {
40407
+ state,
40408
+ fromState
40409
+ }) => {
40410
+ if (navigationType === "push") {
40411
+ return "forward";
40412
+ }
40413
+ if (navigationType !== "traverse") {
40414
+ return null;
40415
+ }
40416
+ const depthIn = navDepthOf(state);
40417
+ const depthOut = navDepthOf(fromState);
40418
+ if (depthIn !== undefined && depthOut !== undefined && depthIn > depthOut) {
40419
+ return "forward";
40420
+ }
40421
+ return "back";
40422
+ };
40343
40423
  const navDepthOf = state => {
40344
40424
  if (state && typeof state[NAV_DEPTH_STATE_KEY] === "number") {
40345
40425
  return state[NAV_DEPTH_STATE_KEY];
@@ -40523,6 +40603,10 @@ let navigationAnimated = false;
40523
40603
  // "push", "replace", "traverse", … — a push is the one navigation that creates
40524
40604
  // the entry a crossing is recorded on.
40525
40605
  let navigationType = null;
40606
+ // Which way the history says this navigation goes — "forward", "back", or null
40607
+ // for a replace — when the default reads it (see readHistoryDirection), and
40608
+ // undefined when nothing does.
40609
+ let navigationHistoryDirection;
40526
40610
  // What was decided for the navigation now landing — a movement, or "none" —
40527
40611
  // which is what its entry remembers (see recordCrossing). Null while nothing
40528
40612
  // has been decided, and for a navigation nothing was said about.
@@ -40540,14 +40624,19 @@ observeBeforeRouting(details => {
40540
40624
  navigationType = details.navigationType;
40541
40625
  navigationUrl = details.url;
40542
40626
  navigationFromUrl = documentUrlSignal.peek();
40543
- // A traversal has no element and no call to ask anything: what it asks is
40544
- // the crossing it retraces — reversed for a back, as it was for a forward.
40545
40627
  // Read before the document state moves, so the state peeked is the entry
40546
40628
  // being left.
40629
+ const fromState = documentStateSignal.peek();
40630
+ // A traversal has no element and no call to ask anything: what it asks is
40631
+ // the crossing it retraces — reversed for a back, as it was for a forward.
40547
40632
  navigationRequest = navigationType === "traverse" ? readTraversalReplay(details, {
40548
40633
  fromUrl: navigationFromUrl,
40549
- fromState: documentStateSignal.peek()
40634
+ fromState
40550
40635
  }) : readNavigationRequest(details);
40636
+ navigationHistoryDirection = defaultTransition && defaultTransition.readsHistory ? readHistoryDirection(navigationType, {
40637
+ state: details.state,
40638
+ fromState
40639
+ }) : undefined;
40551
40640
  if (relations.length === 0 && !defaultTransition && !navigationRequest) {
40552
40641
  return;
40553
40642
  }
@@ -40565,17 +40654,24 @@ observeAfterRouting(() => {
40565
40654
  const url = navigationUrl;
40566
40655
  const fromUrl = navigationFromUrl;
40567
40656
  const type = navigationType;
40657
+ const historyDirection = navigationHistoryDirection;
40568
40658
  // Read here and dropped here: a request answers for the navigation it was
40569
40659
  // made on, and the next one is back to the relations.
40570
40660
  navigationRequest = null;
40571
40661
  navigationUrl = null;
40572
40662
  navigationFromUrl = null;
40573
40663
  navigationType = null;
40574
- if (!navigationAnimated && (request || defaultTransition)) {
40664
+ navigationHistoryDirection = undefined;
40665
+ // A default reading the history plays between two pages a level apart: not
40666
+ // on a replace, and not on a navigation that keeps the page — a push
40667
+ // opening a layer over it moves the layer, not the page (see
40668
+ // page_change.js).
40669
+ const base = defaultTransition && (!defaultTransition.readsHistory || historyDirection !== null && isRenderingPageChange()) ? defaultTransition : null;
40670
+ if (!navigationAnimated && (request || base)) {
40575
40671
  const {
40576
40672
  type,
40577
40673
  duration
40578
- } = resolveTransition(request, defaultTransition);
40674
+ } = resolveTransition(request, base);
40579
40675
  if (type === "none") {
40580
40676
  navigationDecision = {
40581
40677
  type: "none"
@@ -40588,11 +40684,12 @@ observeAfterRouting(() => {
40588
40684
  // A default has no direction: nothing says which of two arbitrary
40589
40685
  // pages is before the other, and the attribute is then worn empty —
40590
40686
  // present for whoever keys on "one of ours is playing", silent on the
40591
- // way. A request is the other case: a navigation IS a way round, so a
40592
- // press that names the movement means forward unless it says
40593
- // otherwise — and a movement of navi's is written on the direction,
40594
- // so left empty it would play nothing at all.
40595
- direction: request && request.direction !== undefined ? request.direction : request && request.typeSaid ? "forward" : "",
40687
+ // way — unless it reads the history. A request is the other case: a
40688
+ // navigation IS a way round, so a press that names the movement means
40689
+ // forward unless it says otherwise — and a movement of navi's is
40690
+ // written on the direction, so left empty it would play nothing at
40691
+ // all.
40692
+ direction: request && request.direction !== undefined ? request.direction : request && request.typeSaid ? "forward" : base && base.readsHistory ? historyDirection : "",
40596
40693
  type,
40597
40694
  duration
40598
40695
  });
@@ -40708,6 +40805,12 @@ const findRelation = (fromPage, toPage) => {
40708
40805
  };
40709
40806
  }
40710
40807
  }
40808
+ // Leaving such a page plays back because leaving it is assumed to be
40809
+ // closing it. With the default reading the history, that is known instead:
40810
+ // a link out of it is a push, one level deeper, and the default answers.
40811
+ if (navigationHistoryDirection !== undefined && navigationHistoryDirection !== "back") {
40812
+ return null;
40813
+ }
40711
40814
  for (const relation of relations) {
40712
40815
  if (relation.from) {
40713
40816
  continue;
@@ -41621,7 +41724,11 @@ const RouteTravel = ({
41621
41724
  // a travel is between two of them, and by the time anything renders the first
41622
41725
  // one is already gone. Written after each render (below), so a subscriber
41623
41726
  // reading it — they all run before Preact flushes — reads the one being left.
41624
- const currentIndexRef = useRef(currentIndex);
41727
+ // The page and not its place in the row: a row given again around the page
41728
+ // on screen (a window over a list, read anew around it) puts that page at
41729
+ // another index without anything having moved.
41730
+ const currentPage = currentIndex === -1 ? null : pages[currentIndex];
41731
+ const currentPageRef = useRef(currentPage);
41625
41732
 
41626
41733
  // Where a page is asked for, whoever asks: a page is a route AND the params
41627
41734
  // that say which of its tabs, and a caller told only the route would send the
@@ -41817,11 +41924,13 @@ const RouteTravel = ({
41817
41924
  if (index === -1) {
41818
41925
  return;
41819
41926
  }
41820
- if (index === currentIndexRef.current) {
41821
- // Where the row already was — the first reading of all, and any move
41822
- // this box has already taken note of (a render writes it too). What it
41823
- // was still waiting for is here nonetheless, so the wait is called off.
41824
- if (samePage(pageAskedForRef.current, pages[index])) {
41927
+ const page = pages[index];
41928
+ if (samePage(page, currentPageRef.current)) {
41929
+ // Where the row already was — the first reading of all, a row given
41930
+ // again around the page on screen, and any move this box has already
41931
+ // taken note of (a render writes it too). What it was still waiting
41932
+ // for is here nonetheless, so the wait is called off.
41933
+ if (samePage(pageAskedForRef.current, page)) {
41825
41934
  pageAskedForRef.current = null;
41826
41935
  }
41827
41936
  return;
@@ -41830,13 +41939,12 @@ const RouteTravel = ({
41830
41939
  // between two of them is a page change the path does not show (see
41831
41940
  // page_change.js).
41832
41941
  notePageChange();
41833
- const page = pages[index];
41834
41942
  // A page this box asked for itself — a travel's own navigation, or one
41835
41943
  // it had given up waiting on: what arrives here is the answer to a
41836
41944
  // question already answered, not somebody going somewhere.
41837
41945
  if (samePage(pageAskedForRef.current, page)) {
41838
41946
  pageAskedForRef.current = null;
41839
- currentIndexRef.current = index;
41947
+ currentPageRef.current = page;
41840
41948
  return;
41841
41949
  }
41842
41950
  // Somewhere else arrived first: whatever this box was still waiting for
@@ -41848,15 +41956,15 @@ const RouteTravel = ({
41848
41956
  // flight is the answer, aimed somewhere else. Starting a second one on
41849
41957
  // top would leave this one's pictures to be dropped mid-slide.
41850
41958
  if (travelRef.current) {
41851
- currentIndexRef.current = index;
41959
+ currentPageRef.current = page;
41852
41960
  retargetTravel(travelRef.current, page);
41853
41961
  return;
41854
41962
  }
41855
- const fromIndex = currentIndexRef.current;
41856
- currentIndexRef.current = index;
41857
- if (fromIndex === -1 || fromIndex === index) {
41858
- // Arriving from outside this row (or not moving at all): there is no
41859
- // pair of pages to show, so there is nothing to travel between.
41963
+ const fromIndex = pageIndexOf(pages, currentPageRef.current);
41964
+ currentPageRef.current = page;
41965
+ if (fromIndex === -1) {
41966
+ // Arriving from outside this row: there is no pair of pages to show,
41967
+ // so there is nothing to travel between.
41860
41968
  return;
41861
41969
  }
41862
41970
  beginTravel({
@@ -41891,8 +41999,8 @@ const RouteTravel = ({
41891
41999
  // What the next announcement will compare itself against: written after the
41892
42000
  // render that shows it, so it is always the page one is looking at.
41893
42001
  useLayoutEffect(() => {
41894
- currentIndexRef.current = currentIndex;
41895
- }, [currentIndex]);
42002
+ currentPageRef.current = currentPage;
42003
+ }, [currentPage]);
41896
42004
 
41897
42005
  // Let go of far enough: the movement carries on from under the finger, at its
41898
42006
  // own pace, to the end.
@@ -44905,13 +45013,15 @@ const navToCommand = (source, event, { argument }) => {
44905
45013
  return {
44906
45014
  target,
44907
45015
  // Which way there is worn by the source, as a link wears it (see
44908
- // link_replace.js, link_return.js, link_open.js):
45016
+ // link_replace.js, link_return.js, link_open.js,
45017
+ // route_transition_request.js):
44909
45018
  // `<Button command="--navi-nav-to:/done" replace>`.
44910
45019
  implementation: () =>
44911
45020
  navTo(argument, {
44912
45021
  replace: linkAsksForReplace(source),
44913
45022
  asReturn: linkAsksForReturn(source),
44914
45023
  open: linkAsksToOpen(source),
45024
+ routeTransition: readRouteTransitionRequest(source),
44915
45025
  }),
44916
45026
  };
44917
45027
  };
@@ -58012,10 +58122,8 @@ const LinkPlain = props => {
58012
58122
 
58013
58123
  // What this link asks of a route transition, worn as an attribute so that
58014
58124
  // the navigation reads it off the element being pressed (see
58015
- // route_transition.jsx, which owns the name and does the reading). A type is
58016
- // a name; anything more travels as JSON, which is also how a plain <a>
58017
- // writes it by hand.
58018
- const routeTransitionRequest = routeTransition === undefined || routeTransition === null ? undefined : typeof routeTransition === "string" ? routeTransition : JSON.stringify(routeTransition);
58125
+ // route_transition_request.js, which owns the name and does the reading).
58126
+ const routeTransitionRequest = writeRouteTransitionRequest(routeTransition);
58019
58127
 
58020
58128
  // Which way this link goes to the place it aims at, worn as an attribute so
58021
58129
  // that whoever answers the press reads it off the anchor (see
@@ -58106,7 +58214,7 @@ const LinkPlain = props => {
58106
58214
  open: undefined,
58107
58215
  document: undefined,
58108
58216
  prefetch: undefined,
58109
- "data-navi-route-transition-request": routeTransitionRequest,
58217
+ ...routeTransitionRequest,
58110
58218
  ...(pressableDuringRouteTransition ? {
58111
58219
  [PRESSABLE_ATTRIBUTE]: ""
58112
58220
  } : null),
@@ -62816,6 +62924,7 @@ const ButtonUI = props => {
62816
62924
  asReturn,
62817
62925
  open,
62818
62926
  document: isDocument,
62927
+ routeTransition,
62819
62928
  pressableDuringRouteTransition,
62820
62929
  prefetch = true,
62821
62930
  // visual
@@ -62873,6 +62982,11 @@ const ButtonUI = props => {
62873
62982
  [LINK_DOCUMENT_ATTRIBUTE]: ""
62874
62983
  } : null;
62875
62984
 
62985
+ // What this press asks of a route transition (see
62986
+ // route_transition_request.js): read off the anchor by the navigation, off
62987
+ // the source by --navi-nav-to.
62988
+ const routeTransitionRequest = writeRouteTransitionRequest(routeTransition);
62989
+
62876
62990
  // Worn as an attribute too, and read at the document by whoever catches the
62877
62991
  // press a movement would have swallowed (see transition_press.js).
62878
62992
  const pressableRequest = pressableDuringRouteTransition ? {
@@ -62929,6 +63043,8 @@ const ButtonUI = props => {
62929
63043
  ...openRequest,
62930
63044
  document: undefined,
62931
63045
  ...documentRequest,
63046
+ routeTransition: undefined,
63047
+ ...routeTransitionRequest,
62932
63048
  pressableDuringRouteTransition: undefined,
62933
63049
  ...pressableRequest,
62934
63050
  prefetch: undefined
@@ -63599,6 +63715,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
63599
63715
  * replace?: boolean,
63600
63716
  * asReturn?: boolean,
63601
63717
  * open?: string | { id: string, type?: "push" | "replace" },
63718
+ * routeTransition?: string | { type?: string, duration?: number | string, direction?: "forward" | "back" },
63602
63719
  * document?: boolean,
63603
63720
  * actionStandalone?: boolean,
63604
63721
  * actionAbortable?: boolean,
@@ -63647,6 +63764,11 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
63647
63764
  * @param {string|{id: string, type?: "push"|"replace"}} [open] Lands with one
63648
63765
  * of the destination's popups open, named the way its `navState` names it:
63649
63766
  * what `<Link open>` says, for a press drawn as a button.
63767
+ * @param {string|{type?: string, duration?: number|string, direction?: "forward"|"back"}} [routeTransition]
63768
+ * What the press asks of a route transition, for that one navigation — a
63769
+ * type name, or an object to also say the pace or which way it plays, over
63770
+ * what the pair's relation says: what `<Link routeTransition>` says, read
63771
+ * off an `href`, a `route` or a `--navi-nav-to` command.
63650
63772
  * @param {boolean} [document] The `href` is ANOTHER DOCUMENT of this origin —
63651
63773
  * an admin panel, a status page, anything built as its own entry point — so
63652
63774
  * the press is left to the browser and loads that page, instead of being