@jsenv/navi 0.29.64 → 0.29.66

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
@@ -29897,9 +29897,12 @@ const css$W = /* css */`
29897
29897
  backdrop-filter: none;
29898
29898
  }
29899
29899
 
29900
+ /* overlay is a no-op here (this backdrop is a plain div, never a
29901
+ top-layer element) but stays in the list so all four transition lists in
29902
+ these files agree — display and overlay always travel together. */
29900
29903
  &[navi-animation] {
29901
29904
  opacity: 1;
29902
- transition-property: display, opacity;
29905
+ transition-property: display, overlay, opacity;
29903
29906
  transition-duration: var(--popup-animation-duration);
29904
29907
  transition-timing-function: ease;
29905
29908
  transition-behavior: allow-discrete;
@@ -30071,7 +30074,7 @@ const css$W = /* css */`
30071
30074
  * @param {import("ignore:preact").ComponentChildren} props.children
30072
30075
  */
30073
30076
  const Dialog = props => {
30074
- import.meta.css = [css$W, "@jsenv/navi/src/layout/dialog.jsx"];
30077
+ import.meta.css = [css$X, "@jsenv/navi/src/layout/dialog.jsx"];
30075
30078
  if (props.openController) {
30076
30079
  return jsx(ControlledDialog, {
30077
30080
  ...props
@@ -31027,7 +31030,7 @@ installImportMetaCssBuild(import.meta);/**
31027
31030
  * and applied.
31028
31031
  */
31029
31032
  let openLocalPopoverCount = 0;
31030
- const css$V = /* css */`
31033
+ const css$W = /* css */`
31031
31034
  @layer navi {
31032
31035
  .navi_popover {
31033
31036
  /* soft: user-configurable preferred max-height. Kept as a *default*
@@ -31305,12 +31308,16 @@ const css$V = /* css */`
31305
31308
  /* navi-animation mirrors the content popover's own resolved value (set
31306
31309
  imperatively in openEffect) — the backdrop only ever fades, regardless
31307
31310
  of which kind it is (translate/scale wouldn't mean anything on it).
31308
- display is included alongside opacity (+ allow-discrete) for the same
31309
- reason popup_css.js's own .navi_popover rule includes it — see this
31310
- file's top comment. */
31311
+ display and overlay are included alongside opacity (+ allow-discrete)
31312
+ for the same reason popup_css.js's own .navi_popover rule includes them
31313
+ — see this file's top comment. They go together: display keeps the
31314
+ backdrop rendered during the exit, overlay keeps it in the top layer
31315
+ (hidePopover() is called synchronously, so without it the backdrop is
31316
+ evicted immediately and, being z-index: unset, loses to FixedBar and to
31317
+ any positioned element later in DOM order for the whole fade). */
31311
31318
  &[navi-animation] {
31312
31319
  opacity: 1;
31313
- transition-property: display, opacity;
31320
+ transition-property: display, overlay, opacity;
31314
31321
  transition-duration: var(--popup-animation-duration);
31315
31322
  transition-timing-function: ease;
31316
31323
  transition-behavior: allow-discrete;
@@ -31460,7 +31467,7 @@ const css$V = /* css */`
31460
31467
  * @param {import("ignore:preact").ComponentChildren} props.children
31461
31468
  */
31462
31469
  const Popover = props => {
31463
- import.meta.css = [css$V, "@jsenv/navi/src/layout/popover.jsx"];
31470
+ import.meta.css = [css$W, "@jsenv/navi/src/layout/popover.jsx"];
31464
31471
  if (props.openController) {
31465
31472
  return jsx(ControlledPopover, {
31466
31473
  ...props
@@ -32479,7 +32486,7 @@ installImportMetaCssBuild(import.meta);/**
32479
32486
  * event, and a caller replacing the body entirely then has one protocol to
32480
32487
  * follow — `--navi-confirm` for yes, anything that closes for no.
32481
32488
  */
32482
- const css$U = /* css */`
32489
+ const css$V = /* css */`
32483
32490
  /* The width lives on the body rather than on the popup, so that custom
32484
32491
  content (which replaces this body entirely) sizes itself instead of
32485
32492
  inheriting a ceiling meant for a sentence-long question. */
@@ -32616,7 +32623,7 @@ const ConfirmPopup = ({
32616
32623
  onAnswer,
32617
32624
  onClosed
32618
32625
  }) => {
32619
- 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"];
32620
32627
  const {
32621
32628
  mode,
32622
32629
  confirmLabel,
@@ -32700,7 +32707,7 @@ const defaultBody = (message, {
32700
32707
  });
32701
32708
  };
32702
32709
 
32703
- installImportMetaCssBuild(import.meta);const css$T = /* css */`
32710
+ installImportMetaCssBuild(import.meta);const css$U = /* css */`
32704
32711
  .action_error {
32705
32712
  margin-top: 0;
32706
32713
  margin-bottom: 20px;
@@ -32725,7 +32732,7 @@ const ActionRenderer = ({
32725
32732
  children,
32726
32733
  disabled
32727
32734
  }) => {
32728
- 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"];
32729
32736
  if (action === undefined) {
32730
32737
  throw new Error("ActionRenderer requires an action to render, but none was provided.");
32731
32738
  }
@@ -36799,7 +36806,7 @@ const ensureDocumentStartViewTransition = () => {
36799
36806
  startViewTransitionPolyfill.isPolyfill = true;
36800
36807
  document.startViewTransition = startViewTransitionPolyfill;
36801
36808
  }
36802
- return startViewTransition$1;
36809
+ return startViewTransition$2;
36803
36810
  };
36804
36811
 
36805
36812
  // A transition a finger is holding still (see route_travel.jsx): it has to be
@@ -36821,7 +36828,7 @@ const holdViewTransition = (release) => {
36821
36828
  };
36822
36829
  };
36823
36830
 
36824
- const startViewTransition$1 = (updateCallback) => {
36831
+ const startViewTransition$2 = (updateCallback) => {
36825
36832
  if (releaseHeldViewTransition) {
36826
36833
  const release = releaseHeldViewTransition;
36827
36834
  releaseHeldViewTransition = null;
@@ -38309,6 +38316,787 @@ const RouteUI = ({
38309
38316
  return element;
38310
38317
  };
38311
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
+ installImportMetaCssBuild(import.meta);/**
38403
+ * How two routes move against each other, said one relation at a time —
38404
+ * without putting them in a row, and without a box in the tree.
38405
+ *
38406
+ * A page one goes INTO (a game, a profile, a place) is entered from wherever
38407
+ * one opened it, and left back out the same way. That is a fact about a PAIR
38408
+ * of pages, and only about the pairs it is written for:
38409
+ *
38410
+ * defineRouteTransition(MY_GAMES_PAGE, GAME_PAGE, "slide-x");
38411
+ * defineRouteTransition(RADAR_PAGE, GAME_PAGE, "slide-x");
38412
+ *
38413
+ * Going from the first page to the second plays forward, the reverse plays
38414
+ * back, and two pages never written in the same relation play nothing between
38415
+ * each other — two tabs of a bottom bar are side by side, neither is before
38416
+ * the other, and being animated by the same mechanism does not order them.
38417
+ * This is what tells this apart from <RouteTravel>: a travel box is a ROW — a
38418
+ * total order, plus a drag gesture that walks it — while this declares
38419
+ * individual relations and nothing else.
38420
+ *
38421
+ * A relation is reciprocal by DEFAULT, not by decree: the way back is the same
38422
+ * movement run the other way, because that is what lets a user build a map of
38423
+ * the app — but a relation written for the exact way travelled wins over being
38424
+ * the reverse of another, so B → A can be given a movement of its own, or
38425
+ * silenced with "none", by writing it (see findRelation).
38426
+ *
38427
+ * The relation says WHEN something plays and which way; the transition says
38428
+ * WHAT plays — a movement navi ships, or a name the application defines in its
38429
+ * own CSS (see the JSDoc below). Said without one, the relation plays the
38430
+ * browser's cross-fade.
38431
+ *
38432
+ * There is no box in the tree: by default what animates is the document itself
38433
+ * (its `root` view transition group), which is right for pages that ARE the
38434
+ * whole viewport. An application whose pages live between fixed bars marks the
38435
+ * region they live in with `data-navi-route-transition-area` — one attribute
38436
+ * on an element it already has — and the movement then plays on that region's
38437
+ * own pictures, clipped at its bounds, while the bars simply never move (see
38438
+ * TRANSITION_AREA_ATTRIBUTE for why the root pictures cannot do this job).
38439
+ *
38440
+ * The URL leads and the picture follows, as everywhere in navi: the change is
38441
+ * a navigation somebody else started (a <Link>, the back button), this only
38442
+ * watches it land and photographs the page being left in time (see
38443
+ * rendering_hold.js for how the picture is kept honest). A browser without
38444
+ * view transitions navigates without the movement.
38445
+ *
38446
+ * However many relations are defined, there is ONE watcher: every definition
38447
+ * lands in a shared registry, and the watcher is rebuilt over the whole of it
38448
+ * — a navigation is a single fact about the document, and the first relation
38449
+ * that speaks about it answers for it.
38450
+ */
38451
+ const startViewTransition$1 = ensureDocumentStartViewTransition();
38452
+ const TRANSITION_ATTRIBUTE = "data-navi-route-transition";
38453
+ const TRANSITION_TYPE_ATTRIBUTE = "data-navi-route-transition-type";
38454
+ const TRANSITION_DURATION_PROPERTY = "--navi-route-transition-duration";
38455
+ // What the movement is played on. The root snapshot spans the viewport, and
38456
+ // the regions of elements captured on their own (a named bar) are BLANK in it
38457
+ // — a page sliding vertically then drags a blank band across the screen where
38458
+ // the top bar was. An application with fixed bars therefore marks the region
38459
+ // its pages live in with this attribute: the marked element is captured on its
38460
+ // own, the movement plays on ITS pictures, clipped at its bounds, and the bars
38461
+ // simply never move. Without the mark the document itself travels, which is
38462
+ // right for a page that IS the whole viewport.
38463
+ const TRANSITION_AREA_ATTRIBUTE = "data-navi-route-transition-area";
38464
+ const TRANSITION_TARGET_ATTRIBUTE = "data-navi-route-transition-target";
38465
+ const AREA_NAME = "navi-route-transition";
38466
+ // route_travel.jsx wears this on the root for the length of one of its
38467
+ // travels (its TRAVEL_ATTRIBUTE — a comment there mirrors this one). Read by
38468
+ // name rather than imported: importing route_travel.jsx would pull the whole
38469
+ // travel machinery into an application that only defines transitions.
38470
+ const ROUTE_TRAVEL_ATTRIBUTE = "data-navi-route-travel";
38471
+
38472
+ // The same movements, written once and played on either target: the document,
38473
+ // or the marked area. The guard keeps the two exclusive — with an area marked,
38474
+ // the root pictures must NOT move (they carry the whole viewport, blank bands
38475
+ // included).
38476
+ const movementsCSS = (name, guard) => /* css */`
38477
+ :root${guard} {
38478
+ &[${TRANSITION_TYPE_ATTRIBUTE}="slide-x"],
38479
+ &[${TRANSITION_TYPE_ATTRIBUTE}="slide-y"],
38480
+ &[${TRANSITION_TYPE_ATTRIBUTE}="cover-x"],
38481
+ &[${TRANSITION_TYPE_ATTRIBUTE}="cover-y"] {
38482
+ &::view-transition-old(${name}),
38483
+ &::view-transition-new(${name}) {
38484
+ /* The default cross-fade, dropped: two pages sliding past each other
38485
+ are two solid things, and seeing through one to the other says they
38486
+ are the same page changing its mind. */
38487
+ mix-blend-mode: normal;
38488
+ animation-timing-function: ease;
38489
+ animation-fill-mode: both;
38490
+ }
38491
+ }
38492
+ &[${TRANSITION_TYPE_ATTRIBUTE}="slide-x"] {
38493
+ &[${TRANSITION_ATTRIBUTE}="forward"] {
38494
+ &::view-transition-old(${name}) {
38495
+ animation-name: navi-route-transition-leave-towards-start;
38496
+ }
38497
+ &::view-transition-new(${name}) {
38498
+ animation-name: navi-route-transition-enter-from-end;
38499
+ }
38500
+ }
38501
+ &[${TRANSITION_ATTRIBUTE}="back"] {
38502
+ &::view-transition-old(${name}) {
38503
+ animation-name: navi-route-transition-leave-towards-end;
38504
+ }
38505
+ &::view-transition-new(${name}) {
38506
+ animation-name: navi-route-transition-enter-from-start;
38507
+ }
38508
+ }
38509
+ }
38510
+ /* The same four movements, along the other axis: the start of a column is
38511
+ its top, so going forward there is the page rising and the next one
38512
+ coming up from below. */
38513
+ &[${TRANSITION_TYPE_ATTRIBUTE}="slide-y"] {
38514
+ &[${TRANSITION_ATTRIBUTE}="forward"] {
38515
+ &::view-transition-old(${name}) {
38516
+ animation-name: navi-route-transition-leave-towards-top;
38517
+ }
38518
+ &::view-transition-new(${name}) {
38519
+ animation-name: navi-route-transition-enter-from-bottom;
38520
+ }
38521
+ }
38522
+ &[${TRANSITION_ATTRIBUTE}="back"] {
38523
+ &::view-transition-old(${name}) {
38524
+ animation-name: navi-route-transition-leave-towards-bottom;
38525
+ }
38526
+ &::view-transition-new(${name}) {
38527
+ animation-name: navi-route-transition-enter-from-top;
38528
+ }
38529
+ }
38530
+ }
38531
+ /* One page over the other, the way a sheet covers a desk: the page
38532
+ arriving slides in ON TOP of one that does not move, and going back it
38533
+ slides off, uncovering it. The still page is animated all the same — to
38534
+ a keyframe that goes nowhere — because left to the browser it would
38535
+ fade. */
38536
+ &[${TRANSITION_TYPE_ATTRIBUTE}="cover-x"] {
38537
+ &[${TRANSITION_ATTRIBUTE}="forward"] {
38538
+ &::view-transition-old(${name}) {
38539
+ animation-name: navi-route-transition-still;
38540
+ }
38541
+ &::view-transition-new(${name}) {
38542
+ animation-name: navi-route-transition-enter-from-end;
38543
+ }
38544
+ }
38545
+ &[${TRANSITION_ATTRIBUTE}="back"] {
38546
+ &::view-transition-old(${name}) {
38547
+ /* The page leaving is the cover: it must slide off ABOVE the one it
38548
+ uncovers, against the browser's default of drawing the new page
38549
+ on top. */
38550
+ z-index: 1;
38551
+ animation-name: navi-route-transition-leave-towards-end;
38552
+ }
38553
+ &::view-transition-new(${name}) {
38554
+ animation-name: navi-route-transition-still;
38555
+ }
38556
+ }
38557
+ }
38558
+ &[${TRANSITION_TYPE_ATTRIBUTE}="cover-y"] {
38559
+ &[${TRANSITION_ATTRIBUTE}="forward"] {
38560
+ &::view-transition-old(${name}) {
38561
+ animation-name: navi-route-transition-still;
38562
+ }
38563
+ &::view-transition-new(${name}) {
38564
+ animation-name: navi-route-transition-enter-from-bottom;
38565
+ }
38566
+ }
38567
+ &[${TRANSITION_ATTRIBUTE}="back"] {
38568
+ &::view-transition-old(${name}) {
38569
+ z-index: 1;
38570
+ animation-name: navi-route-transition-leave-towards-bottom;
38571
+ }
38572
+ &::view-transition-new(${name}) {
38573
+ animation-name: navi-route-transition-still;
38574
+ }
38575
+ }
38576
+ }
38577
+ /* Going deeper is coming closer: the page arriving lands from slightly too
38578
+ big, and going back it is the page leaving that grows away. The other
38579
+ side keeps the browser's own fade under it. */
38580
+ &[${TRANSITION_TYPE_ATTRIBUTE}="zoom"] {
38581
+ &::view-transition-old(${name}),
38582
+ &::view-transition-new(${name}) {
38583
+ animation-fill-mode: both;
38584
+ }
38585
+ &[${TRANSITION_ATTRIBUTE}="forward"] {
38586
+ &::view-transition-new(${name}) {
38587
+ animation-name: navi-route-transition-zoom-in;
38588
+ }
38589
+ }
38590
+ &[${TRANSITION_ATTRIBUTE}="back"] {
38591
+ &::view-transition-old(${name}) {
38592
+ animation-name: navi-route-transition-zoom-out;
38593
+ }
38594
+ }
38595
+ }
38596
+ }
38597
+ `;
38598
+ const css$T = /* css */`
38599
+ /* The marked region is a picture of its own during every view transition of
38600
+ the document — which is what keeps it out of the root snapshot, where its
38601
+ place would be blank. */
38602
+ [${TRANSITION_AREA_ATTRIBUTE}] {
38603
+ view-transition-name: ${AREA_NAME};
38604
+ }
38605
+
38606
+ /* Only while a transition of OURS is playing: everything below changes how
38607
+ the document animates, and the document belongs to the application the
38608
+ rest of the time. The duration is written here, on the direction alone, so
38609
+ a relation with no type — the browser's cross-fade — answers to
38610
+ --navi-route-transition-duration like every other. */
38611
+ :root[${TRANSITION_ATTRIBUTE}] {
38612
+ &::view-transition-old(root),
38613
+ &::view-transition-new(root),
38614
+ &::view-transition-old(${AREA_NAME}),
38615
+ &::view-transition-new(${AREA_NAME}) {
38616
+ animation-duration: var(${TRANSITION_DURATION_PROPERTY}, 300ms);
38617
+ }
38618
+
38619
+ /* The pages are cut at the edge of the area they move in: its pictures are
38620
+ drawn in the top layer, above the bars, and a page sliding in would
38621
+ otherwise be seen crossing them. */
38622
+ &::view-transition-group(${AREA_NAME}),
38623
+ &::view-transition-image-pair(${AREA_NAME}) {
38624
+ overflow: clip;
38625
+ }
38626
+ }
38627
+
38628
+ ${movementsCSS("root", `:not([${TRANSITION_TARGET_ATTRIBUTE}])`)}
38629
+ ${movementsCSS(AREA_NAME, `[${TRANSITION_TARGET_ATTRIBUTE}="area"]`)}
38630
+
38631
+ @keyframes navi-route-transition-leave-towards-start {
38632
+ to {
38633
+ translate: -100% 0;
38634
+ }
38635
+ }
38636
+ @keyframes navi-route-transition-enter-from-end {
38637
+ from {
38638
+ translate: 100% 0;
38639
+ }
38640
+ }
38641
+ @keyframes navi-route-transition-leave-towards-end {
38642
+ to {
38643
+ translate: 100% 0;
38644
+ }
38645
+ }
38646
+ @keyframes navi-route-transition-enter-from-start {
38647
+ from {
38648
+ translate: -100% 0;
38649
+ }
38650
+ }
38651
+ @keyframes navi-route-transition-leave-towards-top {
38652
+ to {
38653
+ translate: 0 -100%;
38654
+ }
38655
+ }
38656
+ @keyframes navi-route-transition-enter-from-bottom {
38657
+ from {
38658
+ translate: 0 100%;
38659
+ }
38660
+ }
38661
+ @keyframes navi-route-transition-leave-towards-bottom {
38662
+ to {
38663
+ translate: 0 100%;
38664
+ }
38665
+ }
38666
+ @keyframes navi-route-transition-enter-from-top {
38667
+ from {
38668
+ translate: 0 -100%;
38669
+ }
38670
+ }
38671
+ @keyframes navi-route-transition-zoom-in {
38672
+ from {
38673
+ opacity: 0;
38674
+ scale: 1.1;
38675
+ }
38676
+ }
38677
+ @keyframes navi-route-transition-zoom-out {
38678
+ to {
38679
+ opacity: 0;
38680
+ scale: 1.1;
38681
+ }
38682
+ }
38683
+ /* Standing still, said as an animation: naming it replaces the browser's own
38684
+ fade on that side, which is the whole point. */
38685
+ @keyframes navi-route-transition-still {
38686
+ to {
38687
+ translate: 0 0;
38688
+ }
38689
+ }
38690
+ `;
38691
+
38692
+ /**
38693
+ * Declare how a pair of routes moves against each other.
38694
+ *
38695
+ * @param {object} from - a route, or `{ route, params }` when the page is a
38696
+ * param of a route rather than a route of its own.
38697
+ * @param {object} to - same forms. Going from `from` to `to` plays forward,
38698
+ * the reverse plays back — unless the reverse is written as a relation of
38699
+ * its own, which then owns that way (a movement of its own, or `"none"` for
38700
+ * a plain cut). A change between two pages no relation was written for plays
38701
+ * nothing.
38702
+ * @param {string|{type?: string, duration?: number|string}} [transition] -
38703
+ * what plays: a type name, or `{ type, duration }` to also say how long
38704
+ * (`--navi-route-transition-duration` says it for everyone otherwise).
38705
+ * Omitted, the browser's own cross-fade. Shipped with navi:
38706
+ * - `"slide-x"`, `"slide-y"`: the two pages slide past each other, forward
38707
+ * towards the start of the axis;
38708
+ * - `"cover-x"`, `"cover-y"`: the page arriving slides in OVER one that does
38709
+ * not move, and slides off it on the way back;
38710
+ * - `"zoom"`: the deeper page is the closer one — it lands from slightly too
38711
+ * big, and grows away when left;
38712
+ * - `"none"`: nothing, said out loud — written on one way of a pair, it cuts
38713
+ * where the reverse of the other way (or the default) would have played;
38714
+ * - `"cross-fade"`: the omitted case, nameable — so one way of a pair can
38715
+ * fade while the other way moves.
38716
+ * Every type plays on the document, or on the element marked
38717
+ * `data-navi-route-transition-area` when the application has one (see the
38718
+ * top of this file). Any other name belongs to the application: for the
38719
+ * length of the transition the root carries
38720
+ * `data-navi-route-transition-type="<type>"` next to
38721
+ * `data-navi-route-transition="forward"|"back"`, and the application's CSS
38722
+ * defines the movement against the view transition pseudo-elements:
38723
+ *
38724
+ * :root[data-navi-route-transition-type="spin"][data-navi-route-transition="forward"] {
38725
+ * &::view-transition-new(root) {
38726
+ * animation-name: my-spin-in;
38727
+ * }
38728
+ * }
38729
+ * @returns {() => void} remove this relation.
38730
+ */
38731
+ const defineRouteTransition = (from, to, transition) => {
38732
+ import.meta.css = [css$T, "@jsenv/navi/src/nav/route_transition.js"];
38733
+ const {
38734
+ type,
38735
+ duration
38736
+ } = normalizeTransition(transition);
38737
+ const relation = {
38738
+ from: normalizePage$1(from),
38739
+ to: normalizePage$1(to),
38740
+ type,
38741
+ duration
38742
+ };
38743
+ relations.push(relation);
38744
+ rebuildWatcher();
38745
+ updateRoutingObservers();
38746
+ return () => {
38747
+ const index = relations.indexOf(relation);
38748
+ if (index > -1) {
38749
+ relations.splice(index, 1);
38750
+ rebuildWatcher();
38751
+ updateRoutingObservers();
38752
+ }
38753
+ };
38754
+ };
38755
+
38756
+ /**
38757
+ * What plays on a navigation no relation was written for: every route change
38758
+ * then plays this transition, and the written relations keep their own.
38759
+ *
38760
+ * A default has no direction — nothing says which of two arbitrary pages is
38761
+ * "before" the other — so give it a movement that does not need one:
38762
+ * `"cross-fade"`, or a custom type whose CSS is keyed on the type alone.
38763
+ *
38764
+ * @param {string|{type?: string, duration?: number|string}} transition - same
38765
+ * forms as defineRouteTransition's. `"none"` (or removing the default) puts
38766
+ * the silence back.
38767
+ * @returns {() => void} remove this default.
38768
+ */
38769
+ const defineRouteDefaultTransition = transition => {
38770
+ import.meta.css = [css$T, "@jsenv/navi/src/nav/route_transition.js"];
38771
+ const value = normalizeTransition(transition);
38772
+ defaultTransition = value;
38773
+ updateRoutingObservers();
38774
+ return () => {
38775
+ if (defaultTransition === value) {
38776
+ defaultTransition = null;
38777
+ updateRoutingObservers();
38778
+ }
38779
+ };
38780
+ };
38781
+
38782
+ // "cross-fade" is a name for what plays when nothing is asked for — the
38783
+ // browser's own animation — so it normalizes to asking for nothing. Having the
38784
+ // name lets one way of a pair say it out loud while the other way slides.
38785
+ const normalizeTransition = transition => {
38786
+ const {
38787
+ type,
38788
+ duration
38789
+ } = typeof transition === "string" ? {
38790
+ type: transition
38791
+ } : transition || {};
38792
+ return {
38793
+ type: type === "cross-fade" ? undefined : type,
38794
+ duration
38795
+ };
38796
+ };
38797
+
38798
+ // Every relation defined, and the single watcher standing over all of them.
38799
+ const relations = [];
38800
+ let watcher = null;
38801
+ const rebuildWatcher = () => {
38802
+ if (watcher) {
38803
+ watcher.stop();
38804
+ watcher = null;
38805
+ }
38806
+ if (relations.length === 0) {
38807
+ return;
38808
+ }
38809
+ // Every page any relation mentions, each once: the position of the current
38810
+ // page in this list is what turns "some signal moved" into "the document
38811
+ // went from page A to page B".
38812
+ const pages = [];
38813
+ for (const {
38814
+ from,
38815
+ to
38816
+ } of relations) {
38817
+ if (pageIndexOf$1(pages, from) === -1) {
38818
+ pages.push(from);
38819
+ }
38820
+ if (pageIndexOf$1(pages, to) === -1) {
38821
+ pages.push(to);
38822
+ }
38823
+ }
38824
+ const currentIndexSignal = computed(() => currentPageIndex$1(pages));
38825
+ let currentIndex;
38826
+ let firstReading = true;
38827
+ const onMove = index => {
38828
+ const fromIndex = currentIndex;
38829
+ currentIndex = index;
38830
+ if (firstReading) {
38831
+ // Where the document already is — nothing changed, there is nothing to
38832
+ // animate. Also the first reading after a definition landed mid-life:
38833
+ // the watcher is standing again on whatever page is current.
38834
+ firstReading = false;
38835
+ return;
38836
+ }
38837
+ if (index === -1 || fromIndex === -1 || fromIndex === index) {
38838
+ return;
38839
+ }
38840
+ const found = findRelation(pages[fromIndex], pages[index]);
38841
+ if (!found) {
38842
+ // No relation says anything about these two: they are side by side, and
38843
+ // silence is the fact — not a missing case.
38844
+ return;
38845
+ }
38846
+ const {
38847
+ direction,
38848
+ relation
38849
+ } = found;
38850
+ if (relation.type === "none") {
38851
+ // Silence said out loud: this way of the pair was written to play
38852
+ // nothing, where the reverse of the other way — or the default — would
38853
+ // have played.
38854
+ navigationAnimated = true;
38855
+ return;
38856
+ }
38857
+ beginTransition({
38858
+ page: pages[index],
38859
+ direction,
38860
+ type: relation.type,
38861
+ duration: relation.duration
38862
+ });
38863
+ };
38864
+ // `subscribe` rather than `effect`: it hands the value to a callback that is
38865
+ // not being tracked, and starting a view transition releases holds that make
38866
+ // the very signals this is watched through move again.
38867
+ const unsubscribe = currentIndexSignal.subscribe(onMove);
38868
+ watcher = {
38869
+ stop: unsubscribe
38870
+ };
38871
+ };
38872
+
38873
+ // What plays when no relation matched (see defineRouteDefaultTransition), and
38874
+ // whether the navigation now landing found an answer already — a relation's
38875
+ // transition, a "none", a RouteTravel travel. The flag is reset when a
38876
+ // navigation begins, so it is always about the latest one.
38877
+ let defaultTransition = null;
38878
+ let navigationAnimated = false;
38879
+
38880
+ // The two ends of a navigation, watched while there is anyone to animate it.
38881
+ // The picture of the page being left has to be honest, so rendering is held
38882
+ // from before the navigation's first write (see rendering_hold.js) — and given
38883
+ // back at the far end when the change turns out to be one nobody animates,
38884
+ // which is also the one moment the DEFAULT can decide: every relation has had
38885
+ // its say by then.
38886
+ let stopRoutingObservers = null;
38887
+ const updateRoutingObservers = () => {
38888
+ const wanted = relations.length > 0 || defaultTransition !== null;
38889
+ if (wanted && !stopRoutingObservers) {
38890
+ const stopWatchingStart = observeBeforeRouting(() => {
38891
+ navigationAnimated = false;
38892
+ holdRenderingForRouting();
38893
+ });
38894
+ const stopWatchingEnd = observeAfterRouting(() => {
38895
+ if (defaultTransition && defaultTransition.type !== "none" && !navigationAnimated) {
38896
+ beginTransition({
38897
+ page: null,
38898
+ // A default has no direction: nothing says which of two arbitrary
38899
+ // pages is before the other. The attribute is worn empty — present
38900
+ // for whoever keys on "one of ours is playing", silent on the way.
38901
+ direction: "",
38902
+ type: defaultTransition.type,
38903
+ duration: defaultTransition.duration
38904
+ });
38905
+ }
38906
+ releaseRoutingRenderingHold();
38907
+ });
38908
+ stopRoutingObservers = () => {
38909
+ stopWatchingStart();
38910
+ stopWatchingEnd();
38911
+ };
38912
+ return;
38913
+ }
38914
+ if (!wanted && stopRoutingObservers) {
38915
+ stopRoutingObservers();
38916
+ stopRoutingObservers = null;
38917
+ }
38918
+ };
38919
+
38920
+ // The exact way travelled first, over the whole registry, and only then the
38921
+ // reverses: a relation written B → A owns that way, and being the reverse of
38922
+ // one written A → B never outranks it. This is what makes reciprocity a
38923
+ // default rather than a decree — write the way back to give it a movement of
38924
+ // its own, or "none" to silence it.
38925
+ const findRelation = (fromPage, toPage) => {
38926
+ for (const relation of relations) {
38927
+ if (samePage$1(relation.from, fromPage) && samePage$1(relation.to, toPage)) {
38928
+ return {
38929
+ direction: "forward",
38930
+ relation
38931
+ };
38932
+ }
38933
+ }
38934
+ for (const relation of relations) {
38935
+ if (samePage$1(relation.from, toPage) && samePage$1(relation.to, fromPage)) {
38936
+ return {
38937
+ direction: "back",
38938
+ relation
38939
+ };
38940
+ }
38941
+ }
38942
+ return null;
38943
+ };
38944
+
38945
+ // The transition whose direction the document is currently wearing. One per
38946
+ // document, as with view transitions themselves: a new one starting takes the
38947
+ // attributes over, and only their owner may take them off.
38948
+ let currentTransition = null;
38949
+ const beginTransition = ({
38950
+ page,
38951
+ direction,
38952
+ type,
38953
+ duration
38954
+ }) => {
38955
+ navigationAnimated = true;
38956
+ const documentElement = document.documentElement;
38957
+ // One navigation, one animator. A RouteTravel box already travelling this
38958
+ // change owns the document's transition — and possibly a finger; starting
38959
+ // one here on top would skip its pictures mid-slide. A pair of routes must
38960
+ // be animated by RouteTravel or by a route transition, never both.
38961
+ if (documentElement.hasAttribute(ROUTE_TRAVEL_ATTRIBUTE)) {
38962
+ 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.");
38963
+ return;
38964
+ }
38965
+ const transition = {};
38966
+ currentTransition = transition;
38967
+ documentElement.setAttribute(TRANSITION_ATTRIBUTE, direction);
38968
+ if (type) {
38969
+ documentElement.setAttribute(TRANSITION_TYPE_ATTRIBUTE, type);
38970
+ }
38971
+ // Looked up per transition, not once: the area is the application's own
38972
+ // element and follows its lifecycle — a page layout without bars has none,
38973
+ // and the movement then plays on the document itself.
38974
+ if (document.querySelector(`[${TRANSITION_AREA_ATTRIBUTE}]`)) {
38975
+ documentElement.setAttribute(TRANSITION_TARGET_ATTRIBUTE, "area");
38976
+ }
38977
+ // A duration of this relation's own, worn for the length of the transition —
38978
+ // and whatever the application had written inline put back afterwards, not
38979
+ // erased.
38980
+ let restoreDuration = null;
38981
+ if (duration !== undefined) {
38982
+ const durationBefore = documentElement.style.getPropertyValue(TRANSITION_DURATION_PROPERTY);
38983
+ documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, typeof duration === "number" ? `${duration}ms` : duration);
38984
+ restoreDuration = () => {
38985
+ if (durationBefore) {
38986
+ documentElement.style.setProperty(TRANSITION_DURATION_PROPERTY, durationBefore);
38987
+ } else {
38988
+ documentElement.style.removeProperty(TRANSITION_DURATION_PROPERTY);
38989
+ }
38990
+ };
38991
+ }
38992
+ const releaseRendering = takeoverRoutingRenderingHold();
38993
+ // Armed from here rather than from inside the callback below: the browser
38994
+ // calls that callback a frame later, and a navigation that has already
38995
+ // been decided renders its page in between — a wait armed then waits for
38996
+ // something that has already happened.
38997
+ const renderWait = armRouteRenderWait$1();
38998
+ const viewTransition = startViewTransition$1(async () => {
38999
+ // The picture the browser is about to take must be of the page that was
39000
+ // asked for, and a route matching is not yet a page rendered. Whatever
39001
+ // is awaited here must be able to resolve without a frame: the document
39002
+ // is frozen for the whole of this callback.
39003
+ try {
39004
+ // Releasing flushes the held render synchronously, so a route that
39005
+ // rendered has already resolved the wait by the next line.
39006
+ releaseRendering();
39007
+ if (page === null) {
39008
+ // A default transition: which page is arriving is unknown, and some
39009
+ // navigations render no route at all (a search param bound to a
39010
+ // signal) — waited on, those would freeze the page until the browser
39011
+ // gives up. The wait is raced with a short timer instead.
39012
+ await Promise.race([renderWait.rendered, waitMs(50)]);
39013
+ } else if (pageIsCurrent$1(page)) {
39014
+ await renderWait.rendered;
39015
+ }
39016
+ } finally {
39017
+ renderWait.stop();
39018
+ }
39019
+ });
39020
+ const end = () => {
39021
+ // Whatever ends it — played out, skipped by another transition starting,
39022
+ // failed before its callback ever ran — the hold is given back and the
39023
+ // document is handed back to the application. Both are idempotent, and
39024
+ // the attributes belong to the LAST transition begun: an earlier one
39025
+ // ending late must not strip what a later one is wearing.
39026
+ renderWait.stop();
39027
+ releaseRendering();
39028
+ if (currentTransition === transition) {
39029
+ currentTransition = null;
39030
+ documentElement.removeAttribute(TRANSITION_ATTRIBUTE);
39031
+ documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
39032
+ documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
39033
+ if (restoreDuration) {
39034
+ restoreDuration();
39035
+ }
39036
+ }
39037
+ };
39038
+ viewTransition.finished.then(end, end);
39039
+ };
39040
+
39041
+ // A route matching is a signal changing; how many passes Preact takes to
39042
+ // answer it is its own business, and the render is the moment the picture can
39043
+ // be taken. Listening starts before the change, or a render landing while the
39044
+ // change settles is a render nobody heard.
39045
+ const armRouteRenderWait$1 = () => {
39046
+ let stopListening;
39047
+ const rendered = new Promise(resolve => {
39048
+ stopListening = observeRouteRender(resolve);
39049
+ });
39050
+ return {
39051
+ rendered,
39052
+ stop: () => stopListening()
39053
+ };
39054
+ };
39055
+ const waitMs = ms => new Promise(resolve => setTimeout(resolve, ms));
39056
+ const normalizePage$1 = page => page.isRoute ? {
39057
+ route: page,
39058
+ params: undefined
39059
+ } : page;
39060
+
39061
+ // Two pages are the same page when they select the same thing, not when they
39062
+ // were written by the same hand.
39063
+ const samePage$1 = (a, b) => {
39064
+ if (a === b) {
39065
+ return true;
39066
+ }
39067
+ if (!a || !b) {
39068
+ return false;
39069
+ }
39070
+ return a.route === b.route && compareTwoJsValues(a.params, b.params);
39071
+ };
39072
+ const pageIndexOf$1 = (pages, page) => pages.findIndex(candidate => samePage$1(candidate, page));
39073
+
39074
+ // Whether this page is the one on screen — same reading as route_travel.jsx's
39075
+ // own: matchingSignal is the necessary condition and is read whatever happens,
39076
+ // params only for a route that matches (the params of a route that does not
39077
+ // match are not params).
39078
+ const pageIsCurrent$1 = ({
39079
+ route,
39080
+ params
39081
+ }) => {
39082
+ if (!route.matchingSignal.value) {
39083
+ return false;
39084
+ }
39085
+ return params ? route.matchesParams(params) : true;
39086
+ };
39087
+ // The FIRST page that answers, and every page read all the same: a page that
39088
+ // is not the current one today is the one that must wake the reader tomorrow.
39089
+ const currentPageIndex$1 = pages => {
39090
+ let currentIndex = -1;
39091
+ for (let i = 0; i < pages.length; i++) {
39092
+ const isCurrent = pageIsCurrent$1(pages[i]);
39093
+ if (isCurrent && currentIndex === -1) {
39094
+ currentIndex = i;
39095
+ }
39096
+ }
39097
+ return currentIndex;
39098
+ };
39099
+
38312
39100
  installImportMetaCssBuild(import.meta);/**
38313
39101
  * Dragging from one route to the next, when the tabs of a page are URLs.
38314
39102
  *
@@ -38348,6 +39136,10 @@ installImportMetaCssBuild(import.meta);/**
38348
39136
  */
38349
39137
  const CAN_KEEP_PICTURE = Boolean(document.startViewTransition && !document.startViewTransition.isPolyfill);
38350
39138
  const startViewTransition = ensureDocumentStartViewTransition();
39139
+
39140
+ // route_transition.js reads this attribute by name (not imported, see its
39141
+ // ROUTE_TRAVEL_ATTRIBUTE) to refuse animating a navigation a travel already
39142
+ // animates — keep the two in sync.
38351
39143
  const TRAVEL_ATTRIBUTE = "data-navi-route-travel";
38352
39144
  // Which way the pages move, said on the document: the pictures of a transition
38353
39145
  // hang off the root, not off the box that travels, so the box's own `axis` has
@@ -38847,8 +39639,7 @@ const RouteTravel = ({
38847
39639
  const rectBefore = elementRef.current.getBoundingClientRect();
38848
39640
  // The hold a navigation already took, if this travel is the answer to one:
38849
39641
  // taking another would be taking a hold on a page that is holding still.
38850
- const releaseRendering = renderingHeldForRouting || holdRendering();
38851
- renderingHeldForRouting = null;
39642
+ const releaseRendering = takeoverRoutingRenderingHold();
38852
39643
  // The picture the browser is about to take must be of the page that was
38853
39644
  // asked for, and a route matching is not yet a page rendered. Watched from
38854
39645
  // here rather than from inside the callback below: the browser calls that
@@ -38991,25 +39782,14 @@ const RouteTravel = ({
38991
39782
  }, [pages]);
38992
39783
 
38993
39784
  // Rendering is held for the length of a navigation, so that whatever picture
38994
- // this box is about to take is of the page being LEFT (see holdRendering).
38995
- // Held from before the navigation's first write, because by the time a route
38996
- // announces that it matches, Preact has already been told and the render is
38997
- // queued — a hold taken then is a hold taken too late.
39785
+ // this box is about to take is of the page being LEFT (see
39786
+ // rendering_hold.js).
38998
39787
  useLayoutEffect(() => {
38999
- const stopWatchingStart = observeBeforeRouting(() => {
39000
- renderingHeldForRouting = holdRendering();
39001
- });
39788
+ const stopWatchingStart = observeBeforeRouting(holdRenderingForRouting);
39002
39789
  // Nobody may have had a picture to take: this navigation is not always one
39003
- // this box travels, and a page held for a change it does not animate is a
39004
- // page that stutters for nothing. Whoever wanted it took it over
39005
- // (beginTravel) while the change was being applied, and left nothing here.
39006
- const stopWatchingEnd = observeAfterRouting(() => {
39007
- const release = renderingHeldForRouting;
39008
- renderingHeldForRouting = null;
39009
- if (release) {
39010
- release();
39011
- }
39012
- });
39790
+ // this box travels. Whoever wanted the hold took it over (beginTravel)
39791
+ // while the change was being applied, and left nothing here.
39792
+ const stopWatchingEnd = observeAfterRouting(releaseRoutingRenderingHold);
39013
39793
  return () => {
39014
39794
  stopWatchingStart();
39015
39795
  stopWatchingEnd();
@@ -39622,54 +40402,6 @@ const releaseTravelGeometry = () => {
39622
40402
  }
39623
40403
  };
39624
40404
 
39625
- // The browser does not take the picture of the page being left when a
39626
- // transition is ASKED for — it takes it at the next frame, just before running
39627
- // the update callback. Preact renders sooner than that, in a microtask: so a
39628
- // change nobody here asked for (a tab pressed, the back button) has already
39629
- // reached the DOM when the picture is taken, and the picture is of the page
39630
- // ARRIVING. Both sides of the travel then show it, and one watches a page slide
39631
- // onto itself.
39632
- //
39633
- // So what Preact has queued waits until the update callback, which is the
39634
- // moment the API is built around — the change belongs inside it. The whole
39635
- // document is held, for the one frame the browser needs: it is about to be
39636
- // frozen under a picture anyway.
39637
- let renderingHold = null;
39638
- // The hold a navigation took on its way in, until a travel takes it over or the
39639
- // navigation turns out to be one nobody here animates.
39640
- let renderingHeldForRouting = null;
39641
- const holdRendering = () => {
39642
- if (renderingHold) {
39643
- return renderingHold.release;
39644
- }
39645
- const debounceRenderingBefore = options.debounceRendering;
39646
- const hold = {
39647
- render: null,
39648
- release: () => {
39649
- // Only the hold that is still standing may be given back: a travel
39650
- // ending after another has taken over must not let go of what it does
39651
- // not hold.
39652
- if (renderingHold !== hold) {
39653
- return;
39654
- }
39655
- renderingHold = null;
39656
- options.debounceRendering = debounceRenderingBefore;
39657
- const {
39658
- render
39659
- } = hold;
39660
- hold.render = null;
39661
- if (render) {
39662
- render();
39663
- }
39664
- }
39665
- };
39666
- renderingHold = hold;
39667
- options.debounceRendering = render => {
39668
- hold.render = render;
39669
- };
39670
- return hold.release;
39671
- };
39672
-
39673
40405
  // The animations of the pictures, asked for again until there are some: they
39674
40406
  // come into existence with the transition, several frames after it was asked
39675
40407
  // for, and the gesture has already begun by then. Kept once found — the set
@@ -73006,5 +73738,5 @@ const UserSvg = () => jsx("svg", {
73006
73738
  })
73007
73739
  });
73008
73740
 
73009
- 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 };
73741
+ 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, 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 };
73010
73742
  //# sourceMappingURL=jsenv_navi.js.map