@jsenv/navi 0.29.65 → 0.29.67

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.
@@ -38,7 +38,7 @@ installImportMetaCssBuild(import.meta);/**
38
38
  * any of these, and a number is the last resort, not the first tool.
39
39
  */
40
40
 
41
- const css$11 = /* css */`
41
+ const css$12 = /* css */`
42
42
  @layer navi {
43
43
  :root {
44
44
  /* A control that overlaps its neighbours (the members of a Group share
@@ -90,7 +90,7 @@ const css$11 = /* css */`
90
90
  }
91
91
  }
92
92
  `;
93
- import.meta.css = [css$11, "@jsenv/navi/src/navi_z_indexes.js"];
93
+ import.meta.css = [css$12, "@jsenv/navi/src/navi_z_indexes.js"];
94
94
 
95
95
  const addIntoArray = (array, ...valuesToAdd) => {
96
96
  if (valuesToAdd.length === 1) {
@@ -359,7 +359,7 @@ installImportMetaCssBuild(import.meta);/**
359
359
  * the very first render and the browser does everything on its own.
360
360
  */
361
361
  const URL_TARGET_ATTRIBUTE = "data-url-target";
362
- const css$10 = /* css */`
362
+ const css$11 = /* css */`
363
363
  @layer navi {
364
364
  [${URL_TARGET_ATTRIBUTE}] {
365
365
  animation: navi_url_target var(--navi-url-target-duration, 2000ms)
@@ -377,7 +377,7 @@ const css$10 = /* css */`
377
377
  }
378
378
  }
379
379
  `;
380
- import.meta.css = [css$10, "@jsenv/navi/src/nav/url_target/url_target.js"];
380
+ import.meta.css = [css$11, "@jsenv/navi/src/nav/url_target/url_target.js"];
381
381
  let urlTargetOptions = {
382
382
  block: "center",
383
383
  behavior: "smooth",
@@ -7753,7 +7753,7 @@ installImportMetaCssBuild(import.meta);/**
7753
7753
  * - Arrow automatically shows when pointing at a valid anchor element
7754
7754
  * - Centers in viewport when no anchor element provided or anchor is too big
7755
7755
  */
7756
- const css$$ = /* css */`
7756
+ const css$10 = /* css */`
7757
7757
  @layer navi {
7758
7758
  .navi_callout {
7759
7759
  /* A callout is parented to what it explains, so it inherits from it — and
@@ -7996,7 +7996,7 @@ const openCallout = (message, {
7996
7996
  skipFocus = false,
7997
7997
  debug = () => {}
7998
7998
  } = {}) => {
7999
- import.meta.css = [css$$, "@jsenv/navi/src/control/rules/callout/callout.js"];
7999
+ import.meta.css = [css$10, "@jsenv/navi/src/control/rules/callout/callout.js"];
8000
8000
  if (debug === true) {
8001
8001
  debug = (e, ...args) => console.debug(`"${e.type}" -> `, ...args);
8002
8002
  }
@@ -20118,7 +20118,7 @@ const setupNetworkMonitoring = () => {
20118
20118
  };
20119
20119
  setupNetworkMonitoring();
20120
20120
 
20121
- installImportMetaCssBuild(import.meta);const css$_ = /* css */`
20121
+ installImportMetaCssBuild(import.meta);const css$$ = /* css */`
20122
20122
  .navi_loading_indicator_fluid_container {
20123
20123
  position: relative;
20124
20124
  display: flex;
@@ -20150,7 +20150,7 @@ const LoadingIndicatorFluid = ({
20150
20150
  visuallyHidden,
20151
20151
  ...rest
20152
20152
  }) => {
20153
- import.meta.css = [css$_, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
20153
+ import.meta.css = [css$$, "@jsenv/navi/src/graphic/loading/loading_indicator_fluid.jsx"];
20154
20154
  const ref = useRef(null);
20155
20155
  // The container dimensions can be deduced from the ref itself as the indicator is absolute inset 0
20156
20156
  const [containerWidth, setContainerWidth] = useState(0);
@@ -20355,7 +20355,7 @@ const LoadingRectangleSvg = ({
20355
20355
  });
20356
20356
  };
20357
20357
 
20358
- installImportMetaCssBuild(import.meta);const css$Z = /* css */`
20358
+ installImportMetaCssBuild(import.meta);const css$_ = /* css */`
20359
20359
  .navi_loading_outline_wrapper {
20360
20360
  position: absolute;
20361
20361
  /* Controls place the outline slightly outside their box, right on top of
@@ -20392,7 +20392,7 @@ installImportMetaCssBuild(import.meta);const css$Z = /* css */`
20392
20392
  }
20393
20393
  `;
20394
20394
  const LoadingOutline = props => {
20395
- import.meta.css = [css$Z, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
20395
+ import.meta.css = [css$_, "@jsenv/navi/src/graphic/loading/loading_outline.jsx"];
20396
20396
  if (props.containerRef) {
20397
20397
  const container = props.containerRef.current;
20398
20398
  if (!container) {
@@ -20682,7 +20682,7 @@ const selectByTextStrings = (element, range, startText, endText) => {
20682
20682
  };
20683
20683
 
20684
20684
  installImportMetaCssBuild(import.meta);// https://jsfiddle.net/v5xzJ/4/
20685
- const css$Y = /* css */`
20685
+ const css$Z = /* css */`
20686
20686
  @layer navi {
20687
20687
  .navi_text {
20688
20688
  &[data-skeleton] {
@@ -21188,7 +21188,7 @@ const TextShrinkWrap = props => {
21188
21188
  });
21189
21189
  };
21190
21190
  const TextUI = props => {
21191
- import.meta.css = [css$Y, "@jsenv/navi/src/text/text.jsx"];
21191
+ import.meta.css = [css$Z, "@jsenv/navi/src/text/text.jsx"];
21192
21192
  let {
21193
21193
  ref,
21194
21194
  spacing,
@@ -27266,7 +27266,7 @@ const getAssociatedLabels = element => {
27266
27266
  return Array.from(element.labels);
27267
27267
  };
27268
27268
 
27269
- installImportMetaCssBuild(import.meta);const css$X = /* css */`
27269
+ installImportMetaCssBuild(import.meta);const css$Y = /* css */`
27270
27270
  @layer navi {
27271
27271
  .navi_button {
27272
27272
  --button-border-radius: var(--navi-control-border-radius);
@@ -27697,7 +27697,7 @@ installImportMetaCssBuild(import.meta);const css$X = /* css */`
27697
27697
  }
27698
27698
  `;
27699
27699
  const ButtonUI = props => {
27700
- import.meta.css = [css$X, "@jsenv/navi/src/control/input/button_ui.jsx"];
27700
+ import.meta.css = [css$Y, "@jsenv/navi/src/control/input/button_ui.jsx"];
27701
27701
  const {
27702
27702
  ref,
27703
27703
  // href/link
@@ -29551,7 +29551,7 @@ installImportMetaCssBuild(import.meta);/**
29551
29551
  * reaches the real container.
29552
29552
  */
29553
29553
  let openLocalDialogCount = 0;
29554
- const css$W = /* css */`
29554
+ const css$X = /* css */`
29555
29555
  @layer navi {
29556
29556
  .navi_dialog {
29557
29557
  /* Min gap between the dialog and the edges of its container. Written
@@ -30074,7 +30074,7 @@ const css$W = /* css */`
30074
30074
  * @param {import("ignore:preact").ComponentChildren} props.children
30075
30075
  */
30076
30076
  const Dialog = props => {
30077
- import.meta.css = [css$W, "@jsenv/navi/src/layout/dialog.jsx"];
30077
+ import.meta.css = [css$X, "@jsenv/navi/src/layout/dialog.jsx"];
30078
30078
  if (props.openController) {
30079
30079
  return jsx(ControlledDialog, {
30080
30080
  ...props
@@ -31030,7 +31030,7 @@ installImportMetaCssBuild(import.meta);/**
31030
31030
  * and applied.
31031
31031
  */
31032
31032
  let openLocalPopoverCount = 0;
31033
- const css$V = /* css */`
31033
+ const css$W = /* css */`
31034
31034
  @layer navi {
31035
31035
  .navi_popover {
31036
31036
  /* soft: user-configurable preferred max-height. Kept as a *default*
@@ -31467,7 +31467,7 @@ const css$V = /* css */`
31467
31467
  * @param {import("ignore:preact").ComponentChildren} props.children
31468
31468
  */
31469
31469
  const Popover = props => {
31470
- import.meta.css = [css$V, "@jsenv/navi/src/layout/popover.jsx"];
31470
+ import.meta.css = [css$W, "@jsenv/navi/src/layout/popover.jsx"];
31471
31471
  if (props.openController) {
31472
31472
  return jsx(ControlledPopover, {
31473
31473
  ...props
@@ -32486,7 +32486,7 @@ installImportMetaCssBuild(import.meta);/**
32486
32486
  * event, and a caller replacing the body entirely then has one protocol to
32487
32487
  * follow — `--navi-confirm` for yes, anything that closes for no.
32488
32488
  */
32489
- const css$U = /* css */`
32489
+ const css$V = /* css */`
32490
32490
  /* The width lives on the body rather than on the popup, so that custom
32491
32491
  content (which replaces this body entirely) sizes itself instead of
32492
32492
  inheriting a ceiling meant for a sentence-long question. */
@@ -32623,7 +32623,7 @@ const ConfirmPopup = ({
32623
32623
  onAnswer,
32624
32624
  onClosed
32625
32625
  }) => {
32626
- import.meta.css = [css$U, "@jsenv/navi/src/action/confirm_popup.jsx"];
32626
+ import.meta.css = [css$V, "@jsenv/navi/src/action/confirm_popup.jsx"];
32627
32627
  const {
32628
32628
  mode,
32629
32629
  confirmLabel,
@@ -32707,7 +32707,7 @@ const defaultBody = (message, {
32707
32707
  });
32708
32708
  };
32709
32709
 
32710
- installImportMetaCssBuild(import.meta);const css$T = /* css */`
32710
+ installImportMetaCssBuild(import.meta);const css$U = /* css */`
32711
32711
  .action_error {
32712
32712
  margin-top: 0;
32713
32713
  margin-bottom: 20px;
@@ -32732,7 +32732,7 @@ const ActionRenderer = ({
32732
32732
  children,
32733
32733
  disabled
32734
32734
  }) => {
32735
- import.meta.css = [css$T, "@jsenv/navi/src/action/action_renderer.jsx"];
32735
+ import.meta.css = [css$U, "@jsenv/navi/src/action/action_renderer.jsx"];
32736
32736
  if (action === undefined) {
32737
32737
  throw new Error("ActionRenderer requires an action to render, but none was provided.");
32738
32738
  }
@@ -36806,7 +36806,7 @@ const ensureDocumentStartViewTransition = () => {
36806
36806
  startViewTransitionPolyfill.isPolyfill = true;
36807
36807
  document.startViewTransition = startViewTransitionPolyfill;
36808
36808
  }
36809
- return startViewTransition$1;
36809
+ return startViewTransition$2;
36810
36810
  };
36811
36811
 
36812
36812
  // A transition a finger is holding still (see route_travel.jsx): it has to be
@@ -36828,7 +36828,7 @@ const holdViewTransition = (release) => {
36828
36828
  };
36829
36829
  };
36830
36830
 
36831
- const startViewTransition$1 = (updateCallback) => {
36831
+ const startViewTransition$2 = (updateCallback) => {
36832
36832
  if (releaseHeldViewTransition) {
36833
36833
  const release = releaseHeldViewTransition;
36834
36834
  releaseHeldViewTransition = null;
@@ -38316,6 +38316,1119 @@ const RouteUI = ({
38316
38316
  return element;
38317
38317
  };
38318
38318
 
38319
+ /**
38320
+ * The document's rendering, held for the one frame a view transition needs.
38321
+ *
38322
+ * The browser does not take the picture of the page being left when a
38323
+ * transition is ASKED for — it takes it at the next frame, just before running
38324
+ * the update callback. Preact renders sooner than that, in a microtask: so a
38325
+ * change nobody asked for (a tab pressed, the back button) has already reached
38326
+ * the DOM when the picture is taken, and the picture is of the page ARRIVING.
38327
+ * Both sides of the animation then show it, and one watches a page slide onto
38328
+ * itself.
38329
+ *
38330
+ * So what Preact has queued waits until the update callback, which is the
38331
+ * moment the API is built around — the change belongs inside it. The whole
38332
+ * document is held: it is about to be frozen under a picture anyway.
38333
+ *
38334
+ * ONE hold for the whole document, whoever animates. The hold is a wrapper
38335
+ * around Preact's `options.debounceRendering`, and two of them installed
38336
+ * independently restore each other in the wrong order when they let go — every
38337
+ * render queued in between is then handed to a wrapper nobody will ever
38338
+ * release. Everything that photographs a navigation (RouteTravel's box, a
38339
+ * route transition) must therefore hold through this module, never through a
38340
+ * wrapper of its own.
38341
+ */
38342
+
38343
+
38344
+ let renderingHold = null;
38345
+ const holdRendering = () => {
38346
+ if (renderingHold) {
38347
+ return renderingHold.release;
38348
+ }
38349
+ const debounceRenderingBefore = options.debounceRendering;
38350
+ const hold = {
38351
+ render: null,
38352
+ release: () => {
38353
+ // Only the hold that is still standing may be given back: a holder
38354
+ // releasing after another has taken over must not let go of what it
38355
+ // does not hold.
38356
+ if (renderingHold !== hold) {
38357
+ return;
38358
+ }
38359
+ renderingHold = null;
38360
+ options.debounceRendering = debounceRenderingBefore;
38361
+ const { render } = hold;
38362
+ hold.render = null;
38363
+ if (render) {
38364
+ render();
38365
+ }
38366
+ },
38367
+ };
38368
+ renderingHold = hold;
38369
+ options.debounceRendering = (render) => {
38370
+ hold.render = render;
38371
+ };
38372
+ return hold.release;
38373
+ };
38374
+
38375
+ // The hold a navigation takes on its way in — from before its first write,
38376
+ // because by the time a route announces that it matches, Preact has already
38377
+ // been told and the render is queued; a hold taken then is a hold taken too
38378
+ // late. Kept here until whoever animates the change takes it over, or the
38379
+ // navigation turns out to be one nobody animates.
38380
+ let routingRenderingHold = null;
38381
+ const holdRenderingForRouting = () => {
38382
+ routingRenderingHold = holdRendering();
38383
+ };
38384
+ // Nobody had a picture to take: a page held for a change it does not animate
38385
+ // is a page that stutters for nothing.
38386
+ const releaseRoutingRenderingHold = () => {
38387
+ const release = routingRenderingHold;
38388
+ routingRenderingHold = null;
38389
+ if (release) {
38390
+ release();
38391
+ }
38392
+ };
38393
+ // An animator takes the navigation's hold as its own — taking another would be
38394
+ // taking a hold on a page that is holding still — or takes a fresh one when
38395
+ // the change it animates is not a navigation.
38396
+ const takeoverRoutingRenderingHold = () => {
38397
+ const release = routingRenderingHold || holdRendering();
38398
+ routingRenderingHold = null;
38399
+ return release;
38400
+ };
38401
+
38402
+ /**
38403
+ * The window two pages are seen through while one replaces the other, measured
38404
+ * once and published for the length of the movement.
38405
+ *
38406
+ * Both ways of moving from one route to another need the same six numbers, so
38407
+ * they are written under the same names and read by the same CSS formulas —
38408
+ * `route_travel.jsx` for a row a finger pushes, `route_transition.jsx` for a
38409
+ * relation between two pages. Only one of them ever plays at a time (there is
38410
+ * one view transition per document), which is what lets them share the names.
38411
+ *
38412
+ * What the numbers are for:
38413
+ *
38414
+ * - **the box the movement happens in**, in the WINDOW's coordinates. The
38415
+ * pictures of a transition are drawn in the top layer, above everything, so
38416
+ * no overflow of the document reaches them: where to cut them can only be
38417
+ * said from outside the document, and this is where that outside is known.
38418
+ * - **the height it is held at**, the taller of the two states. Left to the
38419
+ * browser the window's height animates from one to the other, and a window
38420
+ * that changes size under the pictures cuts the page being left from the
38421
+ * bottom, progressively. It does end up at the arriving height, and that is
38422
+ * right; what must not happen is the user watching it get there.
38423
+ * - **where the page being left WAS**, which is not where the window stands:
38424
+ * the two states are at the same place in the layout without being at the
38425
+ * same place in the window — one page is scrolled and the other is not.
38426
+ * Drawn at the window's own corner, the page being left would be seen
38427
+ * jumping to its top before it begins to leave.
38428
+ *
38429
+ * Only the measuring needs JS, and only for the one moment both states exist:
38430
+ * the page arriving is in the DOM and the transition has not started playing.
38431
+ * Everything DERIVED from these numbers — the band a fixed bar covers, how far
38432
+ * a page travels — is derived in CSS, so the application's own numbers (the
38433
+ * room its bars give back, see layout/safe_area.js) take part in it.
38434
+ */
38435
+
38436
+ const WINDOW_TOP_PROPERTY = "--navi-transition-window-top";
38437
+ const WINDOW_LEFT_PROPERTY = "--navi-transition-window-left";
38438
+ const WINDOW_WIDTH_PROPERTY = "--navi-transition-window-width";
38439
+ const WINDOW_HEIGHT_PROPERTY = "--navi-transition-window-height";
38440
+ const WINDOW_OLD_TOP_PROPERTY = "--navi-transition-window-old-top";
38441
+ const WINDOW_OLD_LEFT_PROPERTY = "--navi-transition-window-old-left";
38442
+ const WINDOW_PROPERTIES = [
38443
+ WINDOW_TOP_PROPERTY,
38444
+ WINDOW_LEFT_PROPERTY,
38445
+ WINDOW_WIDTH_PROPERTY,
38446
+ WINDOW_HEIGHT_PROPERTY,
38447
+ WINDOW_OLD_TOP_PROPERTY,
38448
+ WINDOW_OLD_LEFT_PROPERTY,
38449
+ ];
38450
+
38451
+ // Whose numbers are currently published. The window belongs to the movement
38452
+ // that measured it, and only that one may take it down: a movement ending
38453
+ // after another has replaced it must not wipe numbers the new one is standing
38454
+ // on.
38455
+ let windowOwner = null;
38456
+
38457
+ const holdTransitionWindow = (owner, element, rectBefore) => {
38458
+ const rectAfter = element.getBoundingClientRect();
38459
+ // It cannot be measured from one side alone: a page arriving shorter than
38460
+ // the one it replaces would cut the one leaving, a page arriving taller
38461
+ // would be cut itself.
38462
+ const height =
38463
+ rectBefore.height > rectAfter.height ? rectBefore.height : rectAfter.height;
38464
+ windowOwner = owner;
38465
+ const { style } = document.documentElement;
38466
+ style.setProperty(WINDOW_TOP_PROPERTY, `${rectAfter.top}px`);
38467
+ style.setProperty(WINDOW_LEFT_PROPERTY, `${rectAfter.left}px`);
38468
+ style.setProperty(WINDOW_WIDTH_PROPERTY, `${rectAfter.width}px`);
38469
+ style.setProperty(WINDOW_HEIGHT_PROPERTY, `${height}px`);
38470
+ style.setProperty(WINDOW_OLD_TOP_PROPERTY, `${rectBefore.top}px`);
38471
+ style.setProperty(WINDOW_OLD_LEFT_PROPERTY, `${rectBefore.left}px`);
38472
+ };
38473
+
38474
+ // The live layout takes the box back. A discontinuity by construction — the
38475
+ // window stands at the held height, the box is at its own — and an invisible
38476
+ // one: the page arriving is fully in place, and the strip below it that the
38477
+ // window still covers shows the page leaving only while it is still on screen.
38478
+ const releaseTransitionWindow = (owner) => {
38479
+ if (owner !== windowOwner) {
38480
+ return;
38481
+ }
38482
+ windowOwner = null;
38483
+ const { style } = document.documentElement;
38484
+ for (const property of WINDOW_PROPERTIES) {
38485
+ style.removeProperty(property);
38486
+ }
38487
+ };
38488
+
38489
+ installImportMetaCssBuild(import.meta);/**
38490
+ * How two routes move against each other, said one relation at a time —
38491
+ * without putting them in a row, and without a box in the tree.
38492
+ *
38493
+ * A page one goes INTO (a game, a profile, a place) is entered from wherever
38494
+ * one opened it, and left back out the same way. That is a fact about a PAIR
38495
+ * of pages, and only about the pairs it is written for:
38496
+ *
38497
+ * defineRouteTransition(MY_GAMES_PAGE, GAME_PAGE, "slide-x");
38498
+ * defineRouteTransition(RADAR_PAGE, GAME_PAGE, "slide-x");
38499
+ *
38500
+ * Going from the first page to the second plays forward, the reverse plays
38501
+ * back, and two pages never written in the same relation play nothing between
38502
+ * each other — two tabs of a bottom bar are side by side, neither is before
38503
+ * the other, and being animated by the same mechanism does not order them.
38504
+ * This is what tells this apart from <RouteTravel>: a travel box is a ROW — a
38505
+ * total order, plus a drag gesture that walks it — while this declares
38506
+ * individual relations and nothing else.
38507
+ *
38508
+ * A relation is reciprocal by DEFAULT, not by decree: the way back is the same
38509
+ * movement run the other way, because that is what lets a user build a map of
38510
+ * the app — but a relation written for the exact way travelled wins over being
38511
+ * the reverse of another, so B → A can be given a movement of its own, or
38512
+ * silenced with "none", by writing it (see findRelation).
38513
+ *
38514
+ * The relation says WHEN something plays and which way; the transition says
38515
+ * WHAT plays — a movement navi ships, or a name the application defines in its
38516
+ * own CSS (see the JSDoc below). Said without one, the relation plays the
38517
+ * browser's cross-fade.
38518
+ *
38519
+ * There is no box in the tree: by default what animates is the document itself
38520
+ * (its `root` view transition group), which is right for pages that ARE the
38521
+ * whole viewport. An application whose pages live between fixed bars marks the
38522
+ * region they live in with `data-navi-route-transition-area` — one attribute
38523
+ * on an element it already has — and the movement then plays on that region's
38524
+ * own pictures, clipped at its bounds, while the bars simply never move (see
38525
+ * TRANSITION_AREA_ATTRIBUTE for why the root pictures cannot do this job).
38526
+ *
38527
+ * The URL leads and the picture follows, as everywhere in navi: the change is
38528
+ * a navigation somebody else started (a <Link>, the back button), this only
38529
+ * watches it land and photographs the page being left in time (see
38530
+ * rendering_hold.js for how the picture is kept honest). A browser without
38531
+ * view transitions navigates without the movement.
38532
+ *
38533
+ * However many relations are defined, there is ONE watcher: every definition
38534
+ * lands in a shared registry, and the watcher is rebuilt over the whole of it
38535
+ * — a navigation is a single fact about the document, and the first relation
38536
+ * that speaks about it answers for it.
38537
+ */
38538
+ const startViewTransition$1 = ensureDocumentStartViewTransition();
38539
+ const TRANSITION_ATTRIBUTE = "data-navi-route-transition";
38540
+ const TRANSITION_TYPE_ATTRIBUTE = "data-navi-route-transition-type";
38541
+ const TRANSITION_DURATION_PROPERTY = "--navi-route-transition-duration";
38542
+ // What the movement is played on. The root snapshot spans the viewport, and
38543
+ // the regions of elements captured on their own (a named bar) are BLANK in it
38544
+ // — a page sliding vertically then drags a blank band across the screen where
38545
+ // the top bar was. An application with fixed bars therefore marks the region
38546
+ // its pages live in with this attribute: the marked element is captured on its
38547
+ // own, the movement plays on ITS pictures, clipped at its bounds, and the bars
38548
+ // simply never move. Without the mark the document itself travels, which is
38549
+ // right for a page that IS the whole viewport.
38550
+ const TRANSITION_AREA_ATTRIBUTE = "data-navi-route-transition-area";
38551
+ const TRANSITION_TARGET_ATTRIBUTE = "data-navi-route-transition-target";
38552
+ const AREA_NAME = "navi-route-transition";
38553
+ // route_travel.jsx wears this on the root for the length of one of its
38554
+ // travels (its TRAVEL_ATTRIBUTE — a comment there mirrors this one). Read by
38555
+ // name rather than imported: importing route_travel.jsx would pull the whole
38556
+ // travel machinery into an application that only defines transitions.
38557
+ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
38558
+
38559
+ // The same movements, written once and played on either target: the document,
38560
+ // or the marked area. The guard keeps the two exclusive — with an area marked,
38561
+ // the root pictures must NOT move (they carry the whole viewport, blank bands
38562
+ // included).
38563
+
38564
+ const css$T = /* css */`
38565
+ /* The marked region is a picture of its own during every view transition of
38566
+ the document — which is what keeps it out of the root snapshot, where its
38567
+ place would otherwise be blank. */
38568
+ [data-navi-route-transition-area] {
38569
+ view-transition-name: navi-route-transition;
38570
+ }
38571
+
38572
+ /* Only while a transition of OURS is playing: everything below changes how
38573
+ the document animates, and the document belongs to the application the
38574
+ rest of the time. */
38575
+ :root[data-navi-route-transition] {
38576
+ /* With an area marked, the page AROUND it is not taken as a picture — so
38577
+ exactly one of the two names below exists at a time, and the movements
38578
+ can be written once for both. It is also what a fixed bar wants: a
38579
+ captured element is not painted where it stands and cannot be pointed
38580
+ at either, so a bar photographed with the document is dead for the
38581
+ length of every transition. Left live, it answers as it always did, and
38582
+ nothing shows through where the pages are — the two pictures cover the
38583
+ area's rectangle between them at every moment. Anything around that must
38584
+ ANIMATE rather than stand still gets a view-transition-name of its own,
38585
+ and the browser moves it on the same clock. */
38586
+ &[data-navi-route-transition-target="area"] {
38587
+ view-transition-name: none;
38588
+ }
38589
+
38590
+ &::view-transition-old(root),
38591
+ &::view-transition-new(root),
38592
+ &::view-transition-old(navi-route-transition),
38593
+ &::view-transition-new(navi-route-transition) {
38594
+ /* Written on the direction alone, so a relation with no type — the
38595
+ browser's cross-fade — answers to it like every other. */
38596
+ animation-duration: var(--navi-route-transition-duration, 300ms);
38597
+ }
38598
+
38599
+ /* The window the pages are seen through, when it is an area. Nothing here
38600
+ names \`root\`: the root picture IS the window when the whole document
38601
+ travels, already the size of the screen and already cut by it. */
38602
+ &::view-transition-group(navi-route-transition),
38603
+ &::view-transition-image-pair(navi-route-transition) {
38604
+ /* The pages are cut at the edge of the area they move in. Said HERE and
38605
+ nowhere else: these pictures are drawn in the top layer, so no
38606
+ overflow on any element of the document can reach them. */
38607
+ overflow: clip;
38608
+ }
38609
+ &::view-transition-group(navi-route-transition) {
38610
+ /* Held still for the whole transition, at the taller of the two states,
38611
+ and standing where the area stands (see transition_window.js). Held by
38612
+ dropping the group's animation rather than by winning against it with
38613
+ !important — which also drops its position animation, fine for an area
38614
+ that stays in the same place from one page to the next. */
38615
+ height: var(--navi-transition-window-height);
38616
+ animation-name: none;
38617
+
38618
+ /* Cut at the safe area, on top of being cut at the area's own box. The
38619
+ pictures are drawn in the top layer, so they cover a fixed bar as
38620
+ easily as anything else — and the area runs UNDER the bars by design:
38621
+ that is what a fixed bar is for, and what the room it gives back is
38622
+ for. An area taller than the screen therefore ends below the bottom
38623
+ bar, and a scrolled one starts above the top bar, so the movement
38624
+ would be watched painting over them for its whole length.
38625
+
38626
+ The band left free is the app's own safe area (see
38627
+ layout/safe_area.js) — every kind of furniture at once, not the bars
38628
+ alone, and read rather than asked for, so one that grows, shrinks or
38629
+ unmounts mid-transition is followed without anything being told. What
38630
+ the window cannot know is only where it itself stands, and that is the
38631
+ measured half. */
38632
+ --navi-route-transition-clip-top: max(
38633
+ 0px,
38634
+ var(--navi-safe-area-inset-top) - var(--navi-transition-window-top)
38635
+ );
38636
+ --navi-route-transition-clip-left: max(
38637
+ 0px,
38638
+ var(--navi-safe-area-inset-left) - var(--navi-transition-window-left)
38639
+ );
38640
+ --navi-route-transition-clip-bottom: max(
38641
+ 0px,
38642
+ var(--navi-transition-window-top) +
38643
+ var(--navi-transition-window-height) +
38644
+ var(--navi-safe-area-inset-bottom) - 100dvh
38645
+ );
38646
+ --navi-route-transition-clip-right: max(
38647
+ 0px,
38648
+ var(--navi-transition-window-left) +
38649
+ var(--navi-transition-window-width) +
38650
+ var(--navi-safe-area-inset-right) - 100dvw
38651
+ );
38652
+ clip-path: inset(
38653
+ var(--navi-route-transition-clip-top)
38654
+ var(--navi-route-transition-clip-right)
38655
+ var(--navi-route-transition-clip-bottom)
38656
+ var(--navi-route-transition-clip-left)
38657
+ );
38658
+
38659
+ /* How far a page travels: the WINDOW it is seen through, not its own
38660
+ size. A page is as tall as its content — several screens of it — and a
38661
+ movement measured on the picture would send it thousands of pixels
38662
+ away, off screen for most of the transition and flying past at the
38663
+ end. What one page crossing another means is one window's worth of
38664
+ movement, whatever the pages are made of. Inherited by the pictures,
38665
+ which is where it is used (see the keyframes). */
38666
+ --navi-route-transition-travel-x: calc(
38667
+ var(--navi-transition-window-width) - var(
38668
+ --navi-route-transition-clip-left
38669
+ ) - var(--navi-route-transition-clip-right)
38670
+ );
38671
+ --navi-route-transition-travel-y: calc(
38672
+ var(--navi-transition-window-height) - var(
38673
+ --navi-route-transition-clip-top
38674
+ ) - var(--navi-route-transition-clip-bottom)
38675
+ );
38676
+ }
38677
+ &::view-transition-old(navi-route-transition) {
38678
+ /* Where the area WAS on screen, which is not where the window stands
38679
+ (see transition_window.js). Offset here rather than by \`translate\`,
38680
+ which the movement itself uses. */
38681
+ top: calc(
38682
+ var(--navi-transition-window-old-top) - var(
38683
+ --navi-transition-window-top
38684
+ )
38685
+ );
38686
+ left: calc(
38687
+ var(--navi-transition-window-old-left) - var(
38688
+ --navi-transition-window-left
38689
+ )
38690
+ );
38691
+ }
38692
+
38693
+ /* ------------------------------------------------------------------
38694
+ The movements. One of \`root\` and \`navi-route-transition\` exists at a
38695
+ time (see the opt-out above), so each is written for both.
38696
+ ------------------------------------------------------------------ */
38697
+ &[data-navi-route-transition-type="slide-x"],
38698
+ &[data-navi-route-transition-type="slide-y"],
38699
+ &[data-navi-route-transition-type="cover-x"],
38700
+ &[data-navi-route-transition-type="cover-y"] {
38701
+ &::view-transition-old(root),
38702
+ &::view-transition-new(root),
38703
+ &::view-transition-old(navi-route-transition),
38704
+ &::view-transition-new(navi-route-transition) {
38705
+ width: auto;
38706
+ /* Each picture at the size it was taken at: a page is not resized by
38707
+ the page it crosses. The picture is as wide as the box the browser
38708
+ gives it — the arriving one's — so a page leaving a narrower box (a
38709
+ scrollbar appeared, a side panel closed) would be seen zooming over
38710
+ the length of the movement. Left to the untyped cross-fade, where
38711
+ scaling one picture into the other is the whole idea. */
38712
+ height: auto;
38713
+ object-fit: none;
38714
+ object-position: top left;
38715
+ /* The default cross-fade, dropped: two pages sliding past each other
38716
+ are two solid things, and seeing through one to the other says they
38717
+ are the same page changing its mind. */
38718
+ mix-blend-mode: normal;
38719
+ animation-timing-function: ease;
38720
+ animation-fill-mode: both;
38721
+ }
38722
+ }
38723
+
38724
+ &[data-navi-route-transition-type="slide-x"] {
38725
+ &[data-navi-route-transition="forward"] {
38726
+ &::view-transition-old(root),
38727
+ &::view-transition-old(navi-route-transition) {
38728
+ animation-name: navi-route-transition-leave-towards-start;
38729
+ }
38730
+ &::view-transition-new(root),
38731
+ &::view-transition-new(navi-route-transition) {
38732
+ animation-name: navi-route-transition-enter-from-end;
38733
+ }
38734
+ }
38735
+ &[data-navi-route-transition="back"] {
38736
+ &::view-transition-old(root),
38737
+ &::view-transition-old(navi-route-transition) {
38738
+ animation-name: navi-route-transition-leave-towards-end;
38739
+ }
38740
+ &::view-transition-new(root),
38741
+ &::view-transition-new(navi-route-transition) {
38742
+ animation-name: navi-route-transition-enter-from-start;
38743
+ }
38744
+ }
38745
+ }
38746
+
38747
+ /* The same four movements, along the other axis: the start of a column is
38748
+ its top, so going forward there is the page rising and the next one
38749
+ coming up from below. */
38750
+ &[data-navi-route-transition-type="slide-y"] {
38751
+ &[data-navi-route-transition="forward"] {
38752
+ &::view-transition-old(root),
38753
+ &::view-transition-old(navi-route-transition) {
38754
+ animation-name: navi-route-transition-leave-towards-top;
38755
+ }
38756
+ &::view-transition-new(root),
38757
+ &::view-transition-new(navi-route-transition) {
38758
+ animation-name: navi-route-transition-enter-from-bottom;
38759
+ }
38760
+ }
38761
+ &[data-navi-route-transition="back"] {
38762
+ &::view-transition-old(root),
38763
+ &::view-transition-old(navi-route-transition) {
38764
+ animation-name: navi-route-transition-leave-towards-bottom;
38765
+ }
38766
+ &::view-transition-new(root),
38767
+ &::view-transition-new(navi-route-transition) {
38768
+ animation-name: navi-route-transition-enter-from-top;
38769
+ }
38770
+ }
38771
+ }
38772
+
38773
+ /* One page over the other, the way a sheet covers a desk: the page
38774
+ arriving slides in ON TOP of one that does not move, and going back it
38775
+ slides off, uncovering it. The still page is animated all the same — to
38776
+ a keyframe that goes nowhere — because left to the browser it would
38777
+ fade. */
38778
+ &[data-navi-route-transition-type="cover-x"] {
38779
+ &[data-navi-route-transition="forward"] {
38780
+ &::view-transition-old(root),
38781
+ &::view-transition-old(navi-route-transition) {
38782
+ animation-name: navi-route-transition-still;
38783
+ }
38784
+ &::view-transition-new(root),
38785
+ &::view-transition-new(navi-route-transition) {
38786
+ animation-name: navi-route-transition-enter-from-end;
38787
+ }
38788
+ }
38789
+ &[data-navi-route-transition="back"] {
38790
+ &::view-transition-old(root),
38791
+ &::view-transition-old(navi-route-transition) {
38792
+ /* The page leaving is the cover: it must slide off ABOVE the one it
38793
+ uncovers, against the browser's default of drawing the new page on
38794
+ top. */
38795
+ z-index: 1;
38796
+ animation-name: navi-route-transition-leave-towards-end;
38797
+ }
38798
+ &::view-transition-new(root),
38799
+ &::view-transition-new(navi-route-transition) {
38800
+ animation-name: navi-route-transition-still;
38801
+ }
38802
+ }
38803
+ }
38804
+ &[data-navi-route-transition-type="cover-y"] {
38805
+ &[data-navi-route-transition="forward"] {
38806
+ &::view-transition-old(root),
38807
+ &::view-transition-old(navi-route-transition) {
38808
+ animation-name: navi-route-transition-still;
38809
+ }
38810
+ &::view-transition-new(root),
38811
+ &::view-transition-new(navi-route-transition) {
38812
+ animation-name: navi-route-transition-enter-from-bottom;
38813
+ }
38814
+ }
38815
+ &[data-navi-route-transition="back"] {
38816
+ &::view-transition-old(root),
38817
+ &::view-transition-old(navi-route-transition) {
38818
+ z-index: 1;
38819
+ animation-name: navi-route-transition-leave-towards-bottom;
38820
+ }
38821
+ &::view-transition-new(root),
38822
+ &::view-transition-new(navi-route-transition) {
38823
+ animation-name: navi-route-transition-still;
38824
+ }
38825
+ }
38826
+ }
38827
+
38828
+ /* Going deeper is coming closer: the page arriving lands from slightly too
38829
+ big, and going back it is the page leaving that grows away. The other
38830
+ side keeps the browser's own fade under it. */
38831
+ &[data-navi-route-transition-type="zoom"] {
38832
+ &::view-transition-old(root),
38833
+ &::view-transition-new(root),
38834
+ &::view-transition-old(navi-route-transition),
38835
+ &::view-transition-new(navi-route-transition) {
38836
+ animation-fill-mode: both;
38837
+ }
38838
+ &[data-navi-route-transition="forward"] {
38839
+ &::view-transition-new(root),
38840
+ &::view-transition-new(navi-route-transition) {
38841
+ animation-name: navi-route-transition-zoom-in;
38842
+ }
38843
+ }
38844
+ &[data-navi-route-transition="back"] {
38845
+ &::view-transition-old(root),
38846
+ &::view-transition-old(navi-route-transition) {
38847
+ animation-name: navi-route-transition-zoom-out;
38848
+ }
38849
+ }
38850
+ }
38851
+ }
38852
+
38853
+ /* One window's worth of movement. The fallback is the picture's own size,
38854
+ which is what the window is when the whole document travels: the root
38855
+ picture IS the viewport. */
38856
+ @keyframes navi-route-transition-leave-towards-start {
38857
+ to {
38858
+ translate: calc(-1 * var(--navi-route-transition-travel-x, 100%)) 0;
38859
+ }
38860
+ }
38861
+ @keyframes navi-route-transition-enter-from-end {
38862
+ from {
38863
+ translate: var(--navi-route-transition-travel-x, 100%) 0;
38864
+ }
38865
+ }
38866
+ @keyframes navi-route-transition-leave-towards-end {
38867
+ to {
38868
+ translate: var(--navi-route-transition-travel-x, 100%) 0;
38869
+ }
38870
+ }
38871
+ @keyframes navi-route-transition-enter-from-start {
38872
+ from {
38873
+ translate: calc(-1 * var(--navi-route-transition-travel-x, 100%)) 0;
38874
+ }
38875
+ }
38876
+ @keyframes navi-route-transition-leave-towards-top {
38877
+ to {
38878
+ translate: 0 calc(-1 * var(--navi-route-transition-travel-y, 100%));
38879
+ }
38880
+ }
38881
+ @keyframes navi-route-transition-enter-from-bottom {
38882
+ from {
38883
+ translate: 0 var(--navi-route-transition-travel-y, 100%);
38884
+ }
38885
+ }
38886
+ @keyframes navi-route-transition-leave-towards-bottom {
38887
+ to {
38888
+ translate: 0 var(--navi-route-transition-travel-y, 100%);
38889
+ }
38890
+ }
38891
+ @keyframes navi-route-transition-enter-from-top {
38892
+ from {
38893
+ translate: 0 calc(-1 * var(--navi-route-transition-travel-y, 100%));
38894
+ }
38895
+ }
38896
+ @keyframes navi-route-transition-zoom-in {
38897
+ from {
38898
+ opacity: 0;
38899
+ scale: 1.1;
38900
+ }
38901
+ }
38902
+ @keyframes navi-route-transition-zoom-out {
38903
+ to {
38904
+ opacity: 0;
38905
+ scale: 1.1;
38906
+ }
38907
+ }
38908
+ /* Standing still, said as an animation: naming it replaces the browser's own
38909
+ fade on that side, which is the whole point. */
38910
+ @keyframes navi-route-transition-still {
38911
+ to {
38912
+ translate: 0 0;
38913
+ }
38914
+ }
38915
+ `;
38916
+
38917
+ /**
38918
+ * The region the pages live in — where the movements play.
38919
+ *
38920
+ * Wrap the `<Route>` tree with it in an application that has fixed furniture
38921
+ * (a top bar, a tab bar): the movements then play on THIS element's pictures,
38922
+ * clipped at its bounds, and the bars never move. Without it the document
38923
+ * itself travels, which is right only when the pages are the whole viewport —
38924
+ * with bars around, the moving root picture drags a blank band across the
38925
+ * screen where they stand.
38926
+ *
38927
+ * It is a real box, and it must be: what is photographed and clipped IS its
38928
+ * rectangle. So `display: contents` cannot be used on it — an element with no
38929
+ * box is never captured, the movement plays on nothing and the browser aborts
38930
+ * the transition. Give it the layout the pages need instead — it is a `Box`,
38931
+ * so `flex`, `className`, `style` and the rest are there for that. An
38932
+ * application that already has an element holding its pages can mark that one
38933
+ * with `data-navi-route-transition-area` rather than nesting another.
38934
+ *
38935
+ * @type {import("ignore:preact").FunctionComponent<{ children?: any, [key: string]: any }>}
38936
+ */
38937
+ const RouteTransitionArea = ({
38938
+ children,
38939
+ ...rest
38940
+ }) => {
38941
+ import.meta.css = [css$T, "@jsenv/navi/src/nav/route_transition.jsx"];
38942
+ const props = {
38943
+ ...rest,
38944
+ [TRANSITION_AREA_ATTRIBUTE]: ""
38945
+ };
38946
+ return jsx(Box, {
38947
+ ...props,
38948
+ children: children
38949
+ });
38950
+ };
38951
+
38952
+ /**
38953
+ * Declare how a pair of routes moves against each other.
38954
+ *
38955
+ * @param {object} from - a route, or `{ route, params }` when the page is a
38956
+ * param of a route rather than a route of its own.
38957
+ * @param {object} to - same forms. Going from `from` to `to` plays forward,
38958
+ * the reverse plays back — unless the reverse is written as a relation of
38959
+ * its own, which then owns that way (a movement of its own, or `"none"` for
38960
+ * a plain cut). A change between two pages no relation was written for plays
38961
+ * nothing.
38962
+ * @param {string|{type?: string, duration?: number|string}} [transition] -
38963
+ * what plays: a type name, or `{ type, duration }` to also say how long
38964
+ * (`--navi-route-transition-duration` says it for everyone otherwise).
38965
+ * Omitted, the browser's own cross-fade. Shipped with navi:
38966
+ * - `"slide-x"`, `"slide-y"`: the two pages slide past each other, forward
38967
+ * towards the start of the axis;
38968
+ * - `"cover-x"`, `"cover-y"`: the page arriving slides in OVER one that does
38969
+ * not move, and slides off it on the way back;
38970
+ * - `"zoom"`: the deeper page is the closer one — it lands from slightly too
38971
+ * big, and grows away when left;
38972
+ * - `"none"`: nothing, said out loud — written on one way of a pair, it cuts
38973
+ * where the reverse of the other way (or the default) would have played;
38974
+ * - `"cross-fade"`: the omitted case, nameable — so one way of a pair can
38975
+ * fade while the other way moves.
38976
+ * Every type plays on the document, or on the element marked
38977
+ * `data-navi-route-transition-area` when the application has one (see the
38978
+ * top of this file). Any other name belongs to the application: for the
38979
+ * length of the transition the root carries
38980
+ * `data-navi-route-transition-type="<type>"` next to
38981
+ * `data-navi-route-transition="forward"|"back"`, and the application's CSS
38982
+ * defines the movement against the view transition pseudo-elements:
38983
+ *
38984
+ * :root[data-navi-route-transition-type="spin"][data-navi-route-transition="forward"] {
38985
+ * &::view-transition-new(root) {
38986
+ * animation-name: my-spin-in;
38987
+ * }
38988
+ * }
38989
+ * @returns {() => void} remove this relation.
38990
+ */
38991
+ const defineRouteTransition = (from, to, transition) => {
38992
+ import.meta.css = [css$T, "@jsenv/navi/src/nav/route_transition.jsx"];
38993
+ const {
38994
+ type,
38995
+ duration
38996
+ } = normalizeTransition(transition);
38997
+ const relation = {
38998
+ from: normalizePage$1(from),
38999
+ to: normalizePage$1(to),
39000
+ type,
39001
+ duration
39002
+ };
39003
+ relations.push(relation);
39004
+ rebuildWatcher();
39005
+ updateRoutingObservers();
39006
+ return () => {
39007
+ const index = relations.indexOf(relation);
39008
+ if (index > -1) {
39009
+ relations.splice(index, 1);
39010
+ rebuildWatcher();
39011
+ updateRoutingObservers();
39012
+ }
39013
+ };
39014
+ };
39015
+
39016
+ /**
39017
+ * What plays on a navigation no relation was written for: every route change
39018
+ * then plays this transition, and the written relations keep their own.
39019
+ *
39020
+ * A default has no direction — nothing says which of two arbitrary pages is
39021
+ * "before" the other — so give it a movement that does not need one:
39022
+ * `"cross-fade"`, or a custom type whose CSS is keyed on the type alone.
39023
+ *
39024
+ * @param {string|{type?: string, duration?: number|string}} transition - same
39025
+ * forms as defineRouteTransition's. `"none"` (or removing the default) puts
39026
+ * the silence back.
39027
+ * @returns {() => void} remove this default.
39028
+ */
39029
+ const defineRouteDefaultTransition = transition => {
39030
+ import.meta.css = [css$T, "@jsenv/navi/src/nav/route_transition.jsx"];
39031
+ const value = normalizeTransition(transition);
39032
+ defaultTransition = value;
39033
+ updateRoutingObservers();
39034
+ return () => {
39035
+ if (defaultTransition === value) {
39036
+ defaultTransition = null;
39037
+ updateRoutingObservers();
39038
+ }
39039
+ };
39040
+ };
39041
+
39042
+ // "cross-fade" is a name for what plays when nothing is asked for — the
39043
+ // browser's own animation — so it normalizes to asking for nothing. Having the
39044
+ // name lets one way of a pair say it out loud while the other way slides.
39045
+ const normalizeTransition = transition => {
39046
+ const {
39047
+ type,
39048
+ duration
39049
+ } = typeof transition === "string" ? {
39050
+ type: transition
39051
+ } : transition || {};
39052
+ return {
39053
+ type: type === "cross-fade" ? undefined : type,
39054
+ duration
39055
+ };
39056
+ };
39057
+
39058
+ // Every relation defined, and the single watcher standing over all of them.
39059
+ const relations = [];
39060
+ let watcher = null;
39061
+ const rebuildWatcher = () => {
39062
+ if (watcher) {
39063
+ watcher.stop();
39064
+ watcher = null;
39065
+ }
39066
+ if (relations.length === 0) {
39067
+ return;
39068
+ }
39069
+ // Every page any relation mentions, each once: the position of the current
39070
+ // page in this list is what turns "some signal moved" into "the document
39071
+ // went from page A to page B".
39072
+ const pages = [];
39073
+ for (const {
39074
+ from,
39075
+ to
39076
+ } of relations) {
39077
+ if (pageIndexOf$1(pages, from) === -1) {
39078
+ pages.push(from);
39079
+ }
39080
+ if (pageIndexOf$1(pages, to) === -1) {
39081
+ pages.push(to);
39082
+ }
39083
+ }
39084
+ const currentIndexSignal = computed(() => currentPageIndex$1(pages));
39085
+ let currentIndex;
39086
+ let firstReading = true;
39087
+ const onMove = index => {
39088
+ const fromIndex = currentIndex;
39089
+ currentIndex = index;
39090
+ if (firstReading) {
39091
+ // Where the document already is — nothing changed, there is nothing to
39092
+ // animate. Also the first reading after a definition landed mid-life:
39093
+ // the watcher is standing again on whatever page is current.
39094
+ firstReading = false;
39095
+ return;
39096
+ }
39097
+ if (index === -1 || fromIndex === -1 || fromIndex === index) {
39098
+ return;
39099
+ }
39100
+ const found = findRelation(pages[fromIndex], pages[index]);
39101
+ if (!found) {
39102
+ // No relation says anything about these two: they are side by side, and
39103
+ // silence is the fact — not a missing case.
39104
+ return;
39105
+ }
39106
+ const {
39107
+ direction,
39108
+ relation
39109
+ } = found;
39110
+ if (relation.type === "none") {
39111
+ // Silence said out loud: this way of the pair was written to play
39112
+ // nothing, where the reverse of the other way — or the default — would
39113
+ // have played.
39114
+ navigationAnimated = true;
39115
+ return;
39116
+ }
39117
+ beginTransition({
39118
+ page: pages[index],
39119
+ direction,
39120
+ type: relation.type,
39121
+ duration: relation.duration
39122
+ });
39123
+ };
39124
+ // `subscribe` rather than `effect`: it hands the value to a callback that is
39125
+ // not being tracked, and starting a view transition releases holds that make
39126
+ // the very signals this is watched through move again.
39127
+ const unsubscribe = currentIndexSignal.subscribe(onMove);
39128
+ watcher = {
39129
+ stop: unsubscribe
39130
+ };
39131
+ };
39132
+
39133
+ // What plays when no relation matched (see defineRouteDefaultTransition), and
39134
+ // whether the navigation now landing found an answer already — a relation's
39135
+ // transition, a "none", a RouteTravel travel. The flag is reset when a
39136
+ // navigation begins, so it is always about the latest one.
39137
+ let defaultTransition = null;
39138
+ let navigationAnimated = false;
39139
+
39140
+ // The two ends of a navigation, watched while there is anyone to animate it.
39141
+ // The picture of the page being left has to be honest, so rendering is held
39142
+ // from before the navigation's first write (see rendering_hold.js) — and given
39143
+ // back at the far end when the change turns out to be one nobody animates,
39144
+ // which is also the one moment the DEFAULT can decide: every relation has had
39145
+ // its say by then.
39146
+ let stopRoutingObservers = null;
39147
+ const updateRoutingObservers = () => {
39148
+ const wanted = relations.length > 0 || defaultTransition !== null;
39149
+ if (wanted && !stopRoutingObservers) {
39150
+ const stopWatchingStart = observeBeforeRouting(() => {
39151
+ navigationAnimated = false;
39152
+ holdRenderingForRouting();
39153
+ });
39154
+ const stopWatchingEnd = observeAfterRouting(() => {
39155
+ if (defaultTransition && defaultTransition.type !== "none" && !navigationAnimated) {
39156
+ beginTransition({
39157
+ page: null,
39158
+ // A default has no direction: nothing says which of two arbitrary
39159
+ // pages is before the other. The attribute is worn empty — present
39160
+ // for whoever keys on "one of ours is playing", silent on the way.
39161
+ direction: "",
39162
+ type: defaultTransition.type,
39163
+ duration: defaultTransition.duration
39164
+ });
39165
+ }
39166
+ releaseRoutingRenderingHold();
39167
+ });
39168
+ stopRoutingObservers = () => {
39169
+ stopWatchingStart();
39170
+ stopWatchingEnd();
39171
+ };
39172
+ return;
39173
+ }
39174
+ if (!wanted && stopRoutingObservers) {
39175
+ stopRoutingObservers();
39176
+ stopRoutingObservers = null;
39177
+ }
39178
+ };
39179
+
39180
+ // The exact way travelled first, over the whole registry, and only then the
39181
+ // reverses: a relation written B → A owns that way, and being the reverse of
39182
+ // one written A → B never outranks it. This is what makes reciprocity a
39183
+ // default rather than a decree — write the way back to give it a movement of
39184
+ // its own, or "none" to silence it.
39185
+ const findRelation = (fromPage, toPage) => {
39186
+ for (const relation of relations) {
39187
+ if (samePage$1(relation.from, fromPage) && samePage$1(relation.to, toPage)) {
39188
+ return {
39189
+ direction: "forward",
39190
+ relation
39191
+ };
39192
+ }
39193
+ }
39194
+ for (const relation of relations) {
39195
+ if (samePage$1(relation.from, toPage) && samePage$1(relation.to, fromPage)) {
39196
+ return {
39197
+ direction: "back",
39198
+ relation
39199
+ };
39200
+ }
39201
+ }
39202
+ return null;
39203
+ };
39204
+
39205
+ // The transition whose direction the document is currently wearing. One per
39206
+ // document, as with view transitions themselves: a new one starting takes the
39207
+ // attributes over, and only their owner may take them off.
39208
+ let currentTransition = null;
39209
+ const beginTransition = ({
39210
+ page,
39211
+ direction,
39212
+ type,
39213
+ duration
39214
+ }) => {
39215
+ navigationAnimated = true;
39216
+ const documentElement = document.documentElement;
39217
+ // One navigation, one animator. A RouteTravel box already travelling this
39218
+ // change owns the document's transition — and possibly a finger; starting
39219
+ // one here on top would skip its pictures mid-slide. A pair of routes must
39220
+ // be animated by RouteTravel or by a route transition, never both.
39221
+ if (documentElement.hasAttribute(ROUTE_TRAVEL_ATTRIBUTE)) {
39222
+ 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.");
39223
+ return;
39224
+ }
39225
+ const transition = {};
39226
+ currentTransition = transition;
39227
+ documentElement.setAttribute(TRANSITION_ATTRIBUTE, direction);
39228
+ if (type) {
39229
+ documentElement.setAttribute(TRANSITION_TYPE_ATTRIBUTE, type);
39230
+ }
39231
+ // Looked up per transition, not once: the area is the application's own
39232
+ // element and follows its lifecycle — a page layout without bars has none,
39233
+ // and the movement then plays on the document itself.
39234
+ const areaElements = document.querySelectorAll(`[${TRANSITION_AREA_ATTRIBUTE}]`);
39235
+ if (areaElements.length > 1) {
39236
+ warnOnce("several-areas", `${areaElements.length} elements carry ${TRANSITION_AREA_ATTRIBUTE}. They all take the same view-transition-name, and a name belongs to one element at a time: the browser refuses EVERY view transition of the document while this holds. Mark the one element the pages live in.`);
39237
+ }
39238
+ const areaElement = areaElements.length > 0 ? areaElements[0] : null;
39239
+ // The area as it stands before anything moves: rendering is held, so this is
39240
+ // still the page being left (see holdAreaGeometry).
39241
+ let areaRectBefore = null;
39242
+ if (areaElement) {
39243
+ documentElement.setAttribute(TRANSITION_TARGET_ATTRIBUTE, "area");
39244
+ areaRectBefore = areaElement.getBoundingClientRect();
39245
+ }
39246
+ // A duration of this relation's own, worn for the length of the transition —
39247
+ // and whatever the application had written inline put back afterwards, not
39248
+ // erased.
39249
+ let restoreDuration = null;
39250
+ if (duration !== undefined) {
39251
+ const durationBefore = documentElement.style.getPropertyValue(TRANSITION_DURATION_PROPERTY);
39252
+ documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, typeof duration === "number" ? `${duration}ms` : duration);
39253
+ restoreDuration = () => {
39254
+ if (durationBefore) {
39255
+ documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, durationBefore);
39256
+ } else {
39257
+ documentElement.style.removeProperty(TRANSITION_DURATION_PROPERTY);
39258
+ }
39259
+ };
39260
+ }
39261
+ const releaseRendering = takeoverRoutingRenderingHold();
39262
+ // Armed from here rather than from inside the callback below: the browser
39263
+ // calls that callback a frame later, and a navigation that has already
39264
+ // been decided renders its page in between — a wait armed then waits for
39265
+ // something that has already happened.
39266
+ const renderWait = armRouteRenderWait$1();
39267
+ // What the browser ACTUALLY captured, read once the pictures exist: it is
39268
+ // the only place the two silent misconfigurations show. Both are about the
39269
+ // same thing — a movement playing on pictures that are not the pages.
39270
+ const viewTransitionReady = () => {
39271
+ const capturedNames = capturedViewTransitionNames();
39272
+ if (areaElements.length > 0) {
39273
+ if (!capturedNames.has(AREA_NAME)) {
39274
+ warnOnce("area-not-captured", `The element marked ${TRANSITION_AREA_ATTRIBUTE} was not captured, so the movement plays on nothing. An element is captured only if it generates a box: \`display: contents\` (or an element not rendered) cannot be the area — its rectangle is what gets photographed and clipped.`);
39275
+ }
39276
+ return;
39277
+ }
39278
+ for (const name of capturedNames) {
39279
+ if (name === "root") {
39280
+ continue;
39281
+ }
39282
+ // Something stands still while the whole document travels under it. The
39283
+ // root picture spans the viewport and has a HOLE where that thing was
39284
+ // captured, so what crosses the screen is a blank band.
39285
+ warnOnce("pages-travel-under-named-elements", `The movement plays on the whole document while "${name}" is captured on its own, so a blank band travels where it stands. Wrap the pages in <RouteTransitionArea> (or mark their element with ${TRANSITION_AREA_ATTRIBUTE}) so the movement plays on them rather than on the document.`);
39286
+ break;
39287
+ }
39288
+ };
39289
+ const viewTransition = startViewTransition$1(async () => {
39290
+ // The picture the browser is about to take must be of the page that was
39291
+ // asked for, and a route matching is not yet a page rendered. Whatever
39292
+ // is awaited here must be able to resolve without a frame: the document
39293
+ // is frozen for the whole of this callback.
39294
+ try {
39295
+ // Releasing flushes the held render synchronously, so a route that
39296
+ // rendered has already resolved the wait by the next line.
39297
+ releaseRendering();
39298
+ if (page === null) {
39299
+ // A default transition: which page is arriving is unknown, and some
39300
+ // navigations render no route at all (a search param bound to a
39301
+ // signal) — waited on, those would freeze the page until the browser
39302
+ // gives up. The wait is raced with a short timer instead.
39303
+ await Promise.race([renderWait.rendered, waitMs(50)]);
39304
+ } else if (pageIsCurrent$1(page)) {
39305
+ await renderWait.rendered;
39306
+ }
39307
+ } finally {
39308
+ renderWait.stop();
39309
+ }
39310
+ // The page arriving is in the DOM and the transition has not started
39311
+ // playing: the one moment both states of the area can be known.
39312
+ if (areaElement) {
39313
+ holdTransitionWindow(transition, areaElement, areaRectBefore);
39314
+ }
39315
+ });
39316
+ const end = () => {
39317
+ // Whatever ends it — played out, skipped by another transition starting,
39318
+ // failed before its callback ever ran — the hold is given back and the
39319
+ // document is handed back to the application. Both are idempotent, and
39320
+ // the attributes belong to the LAST transition begun: an earlier one
39321
+ // ending late must not strip what a later one is wearing.
39322
+ renderWait.stop();
39323
+ releaseRendering();
39324
+ if (currentTransition === transition) {
39325
+ currentTransition = null;
39326
+ documentElement.removeAttribute(TRANSITION_ATTRIBUTE);
39327
+ documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
39328
+ documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
39329
+ releaseTransitionWindow(transition);
39330
+ if (restoreDuration) {
39331
+ restoreDuration();
39332
+ }
39333
+ }
39334
+ };
39335
+ viewTransition.ready.then(viewTransitionReady, ignoreSkipped$1);
39336
+ viewTransition.finished.then(end, end);
39337
+ };
39338
+
39339
+ // A transition skipped by another one starting is an outcome, not a failure.
39340
+ const ignoreSkipped$1 = () => {};
39341
+
39342
+ // The names the browser captured, read off the pictures themselves: what was
39343
+ // asked for in CSS and what was taken are not the same question (see
39344
+ // viewTransitionReady).
39345
+ const capturedViewTransitionNames = () => {
39346
+ const names = new Set();
39347
+ for (const animation of document.getAnimations()) {
39348
+ const pseudoElement = animation.effect?.pseudoElement;
39349
+ if (!pseudoElement || !pseudoElement.startsWith("::view-transition")) {
39350
+ continue;
39351
+ }
39352
+ const nameStart = pseudoElement.indexOf("(");
39353
+ if (nameStart === -1) {
39354
+ continue;
39355
+ }
39356
+ names.add(pseudoElement.slice(nameStart + 1, -1));
39357
+ }
39358
+ return names;
39359
+ };
39360
+
39361
+ // Said once per kind, whatever the number of navigations: a misconfiguration
39362
+ // is one fact about the application, and repeating it every time the user
39363
+ // moves would bury it.
39364
+ const warningsSaid = new Set();
39365
+ const warnOnce = (id, message) => {
39366
+ if (warningsSaid.has(id)) {
39367
+ return;
39368
+ }
39369
+ warningsSaid.add(id);
39370
+ console.warn(message);
39371
+ };
39372
+
39373
+ // A route matching is a signal changing; how many passes Preact takes to
39374
+ // answer it is its own business, and the render is the moment the picture can
39375
+ // be taken. Listening starts before the change, or a render landing while the
39376
+ // change settles is a render nobody heard.
39377
+ const armRouteRenderWait$1 = () => {
39378
+ let stopListening;
39379
+ const rendered = new Promise(resolve => {
39380
+ stopListening = observeRouteRender(resolve);
39381
+ });
39382
+ return {
39383
+ rendered,
39384
+ stop: () => stopListening()
39385
+ };
39386
+ };
39387
+ const waitMs = ms => new Promise(resolve => setTimeout(resolve, ms));
39388
+ const normalizePage$1 = page => page.isRoute ? {
39389
+ route: page,
39390
+ params: undefined
39391
+ } : page;
39392
+
39393
+ // Two pages are the same page when they select the same thing, not when they
39394
+ // were written by the same hand.
39395
+ const samePage$1 = (a, b) => {
39396
+ if (a === b) {
39397
+ return true;
39398
+ }
39399
+ if (!a || !b) {
39400
+ return false;
39401
+ }
39402
+ return a.route === b.route && compareTwoJsValues(a.params, b.params);
39403
+ };
39404
+ const pageIndexOf$1 = (pages, page) => pages.findIndex(candidate => samePage$1(candidate, page));
39405
+
39406
+ // Whether this page is the one on screen — same reading as route_travel.jsx's
39407
+ // own: matchingSignal is the necessary condition and is read whatever happens,
39408
+ // params only for a route that matches (the params of a route that does not
39409
+ // match are not params).
39410
+ const pageIsCurrent$1 = ({
39411
+ route,
39412
+ params
39413
+ }) => {
39414
+ if (!route.matchingSignal.value) {
39415
+ return false;
39416
+ }
39417
+ return params ? route.matchesParams(params) : true;
39418
+ };
39419
+ // The FIRST page that answers, and every page read all the same: a page that
39420
+ // is not the current one today is the one that must wake the reader tomorrow.
39421
+ const currentPageIndex$1 = pages => {
39422
+ let currentIndex = -1;
39423
+ for (let i = 0; i < pages.length; i++) {
39424
+ const isCurrent = pageIsCurrent$1(pages[i]);
39425
+ if (isCurrent && currentIndex === -1) {
39426
+ currentIndex = i;
39427
+ }
39428
+ }
39429
+ return currentIndex;
39430
+ };
39431
+
38319
39432
  installImportMetaCssBuild(import.meta);/**
38320
39433
  * Dragging from one route to the next, when the tabs of a page are URLs.
38321
39434
  *
@@ -38355,6 +39468,10 @@ installImportMetaCssBuild(import.meta);/**
38355
39468
  */
38356
39469
  const CAN_KEEP_PICTURE = Boolean(document.startViewTransition && !document.startViewTransition.isPolyfill);
38357
39470
  const startViewTransition = ensureDocumentStartViewTransition();
39471
+
39472
+ // route_transition.js reads this attribute by name (not imported, see its
39473
+ // ROUTE_TRAVEL_ATTRIBUTE) to refuse animating a navigation a travel already
39474
+ // animates — keep the two in sync.
38358
39475
  const TRAVEL_ATTRIBUTE = "data-navi-route-travel";
38359
39476
  // Which way the pages move, said on the document: the pictures of a transition
38360
39477
  // hang off the root, not off the box that travels, so the box's own `axis` has
@@ -38374,19 +39491,6 @@ const DRAGGED_ATTRIBUTE = "data-navi-route-travel-dragged";
38374
39491
  const TURNED_ATTRIBUTE = "data-navi-route-travel-turned";
38375
39492
  // The name the box wears while it travels, and only then (see nameForTravel).
38376
39493
  const TRAVEL_NAME = "navi-route-travel";
38377
- // Where the two boxes of a travel stand in the window, published for the
38378
- // length of it. Measurements only: what is DERIVED from them — where a picture
38379
- // goes, what a bar covers — is derived in the CSS below, so the app's own
38380
- // numbers (the room its fixed bars take) can take part in it. Only the
38381
- // measuring needs JS, and only for the one moment both boxes exist (see
38382
- // holdTravelGeometry).
38383
- const TRAVEL_TOP_PROPERTY = "--navi-route-travel-top";
38384
- const TRAVEL_LEFT_PROPERTY = "--navi-route-travel-left";
38385
- const TRAVEL_WIDTH_PROPERTY = "--navi-route-travel-width";
38386
- const TRAVEL_HEIGHT_PROPERTY = "--navi-route-travel-height";
38387
- const TRAVEL_OLD_TOP_PROPERTY = "--navi-route-travel-old-top";
38388
- const TRAVEL_OLD_LEFT_PROPERTY = "--navi-route-travel-old-left";
38389
- const TRAVEL_GEOMETRY_PROPERTIES = [TRAVEL_TOP_PROPERTY, TRAVEL_LEFT_PROPERTY, TRAVEL_WIDTH_PROPERTY, TRAVEL_HEIGHT_PROPERTY, TRAVEL_OLD_TOP_PROPERTY, TRAVEL_OLD_LEFT_PROPERTY];
38390
39494
  const css$S = /* css */`
38391
39495
  /* The name that makes the page inside this box a picture of its own during a
38392
39496
  transition — rather than part of the one big picture the document takes, so
@@ -38469,10 +39573,16 @@ const css$S = /* css */`
38469
39573
  would be seen jumping back to its top before it even begins to leave.
38470
39574
  Offset here rather than by \`translate\`, which the movement itself uses,
38471
39575
  and at its own size rather than the group's so that nothing is cut off
38472
- the far side of the shift (see holdTravelGeometry). */
38473
- top: calc(var(${TRAVEL_OLD_TOP_PROPERTY}) - var(${TRAVEL_TOP_PROPERTY}));
39576
+ the far side of the shift (see transition_window.js). */
39577
+ top: calc(
39578
+ var(--navi-transition-window-old-top) - var(
39579
+ --navi-transition-window-top
39580
+ )
39581
+ );
38474
39582
  left: calc(
38475
- var(${TRAVEL_OLD_LEFT_PROPERTY}) - var(${TRAVEL_LEFT_PROPERTY})
39583
+ var(--navi-transition-window-old-left) - var(
39584
+ --navi-transition-window-left
39585
+ )
38476
39586
  );
38477
39587
  width: auto;
38478
39588
  }
@@ -38487,7 +39597,7 @@ const css$S = /* css */`
38487
39597
  }
38488
39598
  &::view-transition-group(navi-route-travel) {
38489
39599
  /* The window the two pictures are seen through, held still for the whole
38490
- travel at the taller of the two boxes (see holdTravelGeometry): the group
39600
+ travel at the taller of the two boxes (see transition_window.js): the group
38491
39601
  is what CLIPS, and the browser animates its height from the box being
38492
39602
  left to the box arriving — so the window shrinks under the pictures and
38493
39603
  cuts the page leaving from the bottom, progressively. The box does end
@@ -38498,7 +39608,7 @@ const css$S = /* css */`
38498
39608
  winning against it with !important — which also drops its position
38499
39609
  animation, fine while a travel box stands in the same place from one
38500
39610
  route to the next. */
38501
- height: var(${TRAVEL_HEIGHT_PROPERTY});
39611
+ height: var(--navi-transition-window-height);
38502
39612
 
38503
39613
  /* Cut at the safe area, on top of being cut at the box. The pictures are
38504
39614
  drawn in the top layer, so they cover a fixed bar as easily as anything
@@ -38515,20 +39625,22 @@ const css$S = /* css */`
38515
39625
  only where it itself stands, and that is the measured half. */
38516
39626
  --navi-route-travel-clip-top: max(
38517
39627
  0px,
38518
- var(--navi-safe-area-inset-top) - var(${TRAVEL_TOP_PROPERTY})
39628
+ var(--navi-safe-area-inset-top) - var(--navi-transition-window-top)
38519
39629
  );
38520
39630
  --navi-route-travel-clip-left: max(
38521
39631
  0px,
38522
- var(--navi-safe-area-inset-left) - var(${TRAVEL_LEFT_PROPERTY})
39632
+ var(--navi-safe-area-inset-left) - var(--navi-transition-window-left)
38523
39633
  );
38524
39634
  --navi-route-travel-clip-bottom: max(
38525
39635
  0px,
38526
- var(${TRAVEL_TOP_PROPERTY}) + var(${TRAVEL_HEIGHT_PROPERTY}) +
39636
+ var(--navi-transition-window-top) +
39637
+ var(--navi-transition-window-height) +
38527
39638
  var(--navi-safe-area-inset-bottom) - 100dvh
38528
39639
  );
38529
39640
  --navi-route-travel-clip-right: max(
38530
39641
  0px,
38531
- var(${TRAVEL_LEFT_PROPERTY}) + var(${TRAVEL_WIDTH_PROPERTY}) +
39642
+ var(--navi-transition-window-left) +
39643
+ var(--navi-transition-window-width) +
38532
39644
  var(--navi-safe-area-inset-right) - 100dvw
38533
39645
  );
38534
39646
  clip-path: inset(
@@ -38850,12 +39962,11 @@ const RouteTravel = ({
38850
39962
  }
38851
39963
  pageAskedForRef.current = page;
38852
39964
  // The box as it stands before anything moves: rendering is held, so this is
38853
- // still the page being left (see holdTravelGeometry).
39965
+ // still the page being left (see transition_window.js).
38854
39966
  const rectBefore = elementRef.current.getBoundingClientRect();
38855
39967
  // The hold a navigation already took, if this travel is the answer to one:
38856
39968
  // taking another would be taking a hold on a page that is holding still.
38857
- const releaseRendering = renderingHeldForRouting || holdRendering();
38858
- renderingHeldForRouting = null;
39969
+ const releaseRendering = takeoverRoutingRenderingHold();
38859
39970
  // The picture the browser is about to take must be of the page that was
38860
39971
  // asked for, and a route matching is not yet a page rendered. Watched from
38861
39972
  // here rather than from inside the callback below: the browser calls that
@@ -38879,7 +39990,7 @@ const RouteTravel = ({
38879
39990
  }, renderWait);
38880
39991
  // The page arriving is in the DOM and the transition has not started
38881
39992
  // playing: the one moment both boxes can be known.
38882
- holdTravelGeometry(elementRef.current, rectBefore);
39993
+ holdTransitionWindow(travel, elementRef.current, rectBefore);
38883
39994
  });
38884
39995
  travel.viewTransition = viewTransition;
38885
39996
  if (scrub) {
@@ -38998,25 +40109,14 @@ const RouteTravel = ({
38998
40109
  }, [pages]);
38999
40110
 
39000
40111
  // Rendering is held for the length of a navigation, so that whatever picture
39001
- // this box is about to take is of the page being LEFT (see holdRendering).
39002
- // Held from before the navigation's first write, because by the time a route
39003
- // announces that it matches, Preact has already been told and the render is
39004
- // queued — a hold taken then is a hold taken too late.
40112
+ // this box is about to take is of the page being LEFT (see
40113
+ // rendering_hold.js).
39005
40114
  useLayoutEffect(() => {
39006
- const stopWatchingStart = observeBeforeRouting(() => {
39007
- renderingHeldForRouting = holdRendering();
39008
- });
40115
+ const stopWatchingStart = observeBeforeRouting(holdRenderingForRouting);
39009
40116
  // Nobody may have had a picture to take: this navigation is not always one
39010
- // this box travels, and a page held for a change it does not animate is a
39011
- // page that stutters for nothing. Whoever wanted it took it over
39012
- // (beginTravel) while the change was being applied, and left nothing here.
39013
- const stopWatchingEnd = observeAfterRouting(() => {
39014
- const release = renderingHeldForRouting;
39015
- renderingHeldForRouting = null;
39016
- if (release) {
39017
- release();
39018
- }
39019
- });
40117
+ // this box travels. Whoever wanted the hold took it over (beginTravel)
40118
+ // while the change was being applied, and left nothing here.
40119
+ const stopWatchingEnd = observeAfterRouting(releaseRoutingRenderingHold);
39020
40120
  return () => {
39021
40121
  stopWatchingStart();
39022
40122
  stopWatchingEnd();
@@ -39208,7 +40308,7 @@ const RouteTravel = ({
39208
40308
  document.documentElement.removeAttribute(TRAVEL_AXIS_ATTRIBUTE);
39209
40309
  document.documentElement.removeAttribute(DRAGGED_ATTRIBUTE);
39210
40310
  document.documentElement.removeAttribute(TURNED_ATTRIBUTE);
39211
- releaseTravelGeometry();
40311
+ releaseTransitionWindow(travel);
39212
40312
  }
39213
40313
  };
39214
40314
 
@@ -39591,92 +40691,6 @@ const releaseHold = travel => {
39591
40691
  document.documentElement.removeAttribute(HOLD_ATTRIBUTE);
39592
40692
  };
39593
40693
 
39594
- // The two boxes of a travel, measured at the one moment both exist: the
39595
- // arriving page is in the DOM and the transition has not started playing.
39596
- //
39597
- // The group stands at the ARRIVING box — its own animation is dropped, so it
39598
- // takes the geometry the browser declared for it and holds it for the whole
39599
- // travel. That is why both rectangles have to be published: a group that does
39600
- // not move says nothing about where the page being left was, and its rectangle
39601
- // in the window is the only thing CSS cannot work out on its own.
39602
- const holdTravelGeometry = (element, rectBefore) => {
39603
- const rectAfter = element.getBoundingClientRect();
39604
- // The height it is held at is the taller of the two boxes, so neither picture
39605
- // is ever cut. It cannot be measured from one side alone: a page arriving
39606
- // shorter than the one it replaces would cut the one leaving, a page arriving
39607
- // taller would be cut itself.
39608
- const height = rectBefore.height > rectAfter.height ? rectBefore.height : rectAfter.height;
39609
- const {
39610
- style
39611
- } = document.documentElement;
39612
- style.setProperty(TRAVEL_TOP_PROPERTY, `${rectAfter.top}px`);
39613
- style.setProperty(TRAVEL_LEFT_PROPERTY, `${rectAfter.left}px`);
39614
- style.setProperty(TRAVEL_WIDTH_PROPERTY, `${rectAfter.width}px`);
39615
- style.setProperty(TRAVEL_HEIGHT_PROPERTY, `${height}px`);
39616
- style.setProperty(TRAVEL_OLD_TOP_PROPERTY, `${rectBefore.top}px`);
39617
- style.setProperty(TRAVEL_OLD_LEFT_PROPERTY, `${rectBefore.left}px`);
39618
- };
39619
- // The live layout takes the box back. A discontinuity by construction — the
39620
- // group stands at the held height, the box is at the new one — and an invisible
39621
- // one: the page arriving is fully in place, and the strip below it that the
39622
- // group still covers shows the page leaving only while it is still on screen.
39623
- const releaseTravelGeometry = () => {
39624
- const {
39625
- style
39626
- } = document.documentElement;
39627
- for (const property of TRAVEL_GEOMETRY_PROPERTIES) {
39628
- style.removeProperty(property);
39629
- }
39630
- };
39631
-
39632
- // The browser does not take the picture of the page being left when a
39633
- // transition is ASKED for — it takes it at the next frame, just before running
39634
- // the update callback. Preact renders sooner than that, in a microtask: so a
39635
- // change nobody here asked for (a tab pressed, the back button) has already
39636
- // reached the DOM when the picture is taken, and the picture is of the page
39637
- // ARRIVING. Both sides of the travel then show it, and one watches a page slide
39638
- // onto itself.
39639
- //
39640
- // So what Preact has queued waits until the update callback, which is the
39641
- // moment the API is built around — the change belongs inside it. The whole
39642
- // document is held, for the one frame the browser needs: it is about to be
39643
- // frozen under a picture anyway.
39644
- let renderingHold = null;
39645
- // The hold a navigation took on its way in, until a travel takes it over or the
39646
- // navigation turns out to be one nobody here animates.
39647
- let renderingHeldForRouting = null;
39648
- const holdRendering = () => {
39649
- if (renderingHold) {
39650
- return renderingHold.release;
39651
- }
39652
- const debounceRenderingBefore = options.debounceRendering;
39653
- const hold = {
39654
- render: null,
39655
- release: () => {
39656
- // Only the hold that is still standing may be given back: a travel
39657
- // ending after another has taken over must not let go of what it does
39658
- // not hold.
39659
- if (renderingHold !== hold) {
39660
- return;
39661
- }
39662
- renderingHold = null;
39663
- options.debounceRendering = debounceRenderingBefore;
39664
- const {
39665
- render
39666
- } = hold;
39667
- hold.render = null;
39668
- if (render) {
39669
- render();
39670
- }
39671
- }
39672
- };
39673
- renderingHold = hold;
39674
- options.debounceRendering = render => {
39675
- hold.render = render;
39676
- };
39677
- return hold.release;
39678
- };
39679
-
39680
40694
  // The animations of the pictures, asked for again until there are some: they
39681
40695
  // come into existence with the transition, several frames after it was asked
39682
40696
  // for, and the gesture has already begun by then. Kept once found — the set
@@ -73013,5 +74027,5 @@ const UserSvg = () => jsx("svg", {
73013
74027
  })
73014
74028
  });
73015
74029
 
73016
- 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, SplitButton, 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 };
74030
+ 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, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, 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, defineRouteDefaultTransition, defineRouteTransition, 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 };
73017
74031
  //# sourceMappingURL=jsenv_navi.js.map