@jsenv/navi 0.29.14 → 0.29.15

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.
@@ -13764,6 +13764,25 @@ const css$V = /* css */`
13764
13764
  * @param {Function} [options.onClose] - Callback when callout is closed
13765
13765
  * @param {boolean} [options.closeOnClickOutside] - Whether to close on outside clicks (defaults to true for "info" status)
13766
13766
  * @param {boolean} [options.debug=false] - Enable debug logging
13767
+ *
13768
+ * Positioning is also driven by attributes read on the anchor element itself
13769
+ * (so markup can tune a callout without going through this function):
13770
+ * - `data-callout-arrow-x="start" | "center" | "end"`: where the arrow points
13771
+ * horizontally along the anchor — `start`/`end` target the left/right edge of
13772
+ * the anchor's text content (inside its borders and padding), `center` targets
13773
+ * the middle of the anchor. Without it the arrow follows the anchor's computed
13774
+ * `text-align` (`center` → center, `right`/`end` → end, anything else → start),
13775
+ * so it lands where the text visually begins. The arrow is always clamped to
13776
+ * stay within the callout's own rounded corners, so a value pointing outside
13777
+ * the callout's width sticks to the nearest side.
13778
+ * - `data-callout-position="top" | "bottom" | …`: preferred side of the anchor
13779
+ * (defaults to `"bottom"`, flipped when there isn't enough space).
13780
+ * - `data-callout-position-fixed`: opt out of that flipping.
13781
+ * - `data-callout-anchor="<selector>"`: point at an inner element instead of the
13782
+ * anchor itself.
13783
+ * - `data-callout-point-to-border-box` / `data-callout-point-to-content-box`:
13784
+ * which box the callout aligns to.
13785
+ * - `data-callout-viewport-spacing="<number>"`: minimum gap with the viewport edges.
13767
13786
  * @returns {Object} - Callout object with properties:
13768
13787
  * - {Function} close - Function to close the callout
13769
13788
  * - {Function} update - Function to update message and options
@@ -35842,10 +35861,13 @@ installImportMetaCssBuild(import.meta);const css$N = /* css */`
35842
35861
  --link-background: unset;
35843
35862
  --link-background-current: unset;
35844
35863
  --link-background-selected: light-dark(#bbdefb, #2563eb);
35845
- --link-color: rgb(0, 0, 238);
35846
- --link-color-visited: color-mix(in srgb, var(--link-color), black 40%);
35864
+ --link-color: var(--navi-link-color);
35865
+ --link-color-visited: var(
35866
+ --navi-link-color-visited,
35867
+ color-mix(in srgb, var(--link-color), black 40%)
35868
+ );
35847
35869
 
35848
- --link-color-pressed: red;
35870
+ --link-color-pressed: var(--navi-link-color-pressed);
35849
35871
  --link-text-decoration: underline;
35850
35872
  --link-text-decoration-hover: var(--link-text-decoration);
35851
35873
  --link-cursor: pointer;
@@ -35854,7 +35876,7 @@ installImportMetaCssBuild(import.meta);const css$N = /* css */`
35854
35876
 
35855
35877
  --link-current-indicator-size: 2px;
35856
35878
  --link-current-indicator-spacing: 0;
35857
- --link-current-indicator-color: rgb(205, 52, 37);
35879
+ --link-current-indicator-color: var(--navi-link-current-indicator-color);
35858
35880
  }
35859
35881
  }
35860
35882
 
@@ -38898,7 +38920,7 @@ installImportMetaCssBuild(import.meta);const css$F = /* css */`
38898
38920
  --loader-color: var(--navi-loader-color);
38899
38921
  --border-color: var(--navi-control-border-color);
38900
38922
  --background-color: white;
38901
- --accent-color: light-dark(#4476ff, #3b82f6);
38923
+ --accent-color: var(--navi-control-accent-color);
38902
38924
  --background-color-checked: var(--accent-color);
38903
38925
  --border-color-checked: var(--accent-color);
38904
38926
  --checkmark-color: white;
@@ -39638,7 +39660,7 @@ installImportMetaCssBuild(import.meta);const css$D = /* css */`
39638
39660
  --border-color: var(--navi-control-border-color);
39639
39661
  --background-color: white;
39640
39662
  --background-color-checked: var(--background-color);
39641
- --accent-color: light-dark(#4476ff, #3b82f6);
39663
+ --accent-color: var(--navi-control-accent-color);
39642
39664
  --radiomark-color: var(--accent-color);
39643
39665
  --border-color-checked: var(--accent-color);
39644
39666
  --cursor: pointer;
@@ -40150,7 +40172,7 @@ installImportMetaCssBuild(import.meta);const css$C = /* css */`
40150
40172
  --font-family: var(--navi-control-font-family);
40151
40173
 
40152
40174
  --loader-color: var(--navi-loader-color);
40153
- --accent-color: rgb(24, 117, 255);
40175
+ --accent-color: var(--navi-control-accent-color);
40154
40176
  --color-mix-light: black;
40155
40177
  --color-mix-dark: white;
40156
40178
  --color-mix: var(--color-mix-dark);
@@ -42983,6 +43005,13 @@ installImportMetaCssBuild(import.meta);/**
42983
43005
  * the map one would have drawn for a line, so it is drawn here and everything
42984
43006
  * below only ever knows about maps.
42985
43007
  *
43008
+ * A travel is ONE BOX long, whatever the distance between the two slides on the
43009
+ * map: the slide arriving is placed next to the one being left for the duration
43010
+ * and put back where the map says afterwards, so a tab bar jumping from the
43011
+ * first tab to the last shows those two and nothing else. Nobody wants to watch
43012
+ * the slides in between fly past — least of all in a tab bar, where they are
43013
+ * not a road one travels but places one goes straight to.
43014
+ *
42986
43015
  * The slides live INSIDE the box, which is what makes this work for a popup: a
42987
43016
  * dialog and a popover are both promoted to the browser's top layer, so no
42988
43017
  * container of ours could ever hold two of them side by side and translate the
@@ -43086,6 +43115,18 @@ const css$z = /* css */`
43086
43115
  slides. */
43087
43116
  translate: var(--slide-offset, 0);
43088
43117
  }
43118
+ /* Off stage: everything but the slide one is looking at and, while the
43119
+ track moves, the slide one is leaving. A travel is one box long
43120
+ whatever the distance on the map (the two are placed a box apart for
43121
+ the occasion, see the layout effect), so the slides in between are
43122
+ never crossed — but on a map wider than one box they would still sit
43123
+ in the frame, and a tab bar jumping from the first tab to the last must
43124
+ show those two and nothing else.
43125
+ visibility, not display: this box is measured on its LARGEST slide, and
43126
+ a slide taken out of the layout would take its size out with it. */
43127
+ > [data-slide][data-slide-offstage] {
43128
+ visibility: hidden;
43129
+ }
43089
43130
  /* Nothing here for a slide not on screen: [inert] (set from JS) already
43090
43131
  takes it out of reach of the pointer, of Tab and of a screen reader —
43091
43132
  one attribute instead of pointer-events plus aria-hidden, and the only
@@ -43317,6 +43358,25 @@ const SlideContainer = ({
43317
43358
  // mid-travel would read a moving value.
43318
43359
  const offsetRef = useRef();
43319
43360
  const trackAnimationRef = useRef(null);
43361
+ // Where the slides are while the track moves, which is not where the map says
43362
+ // they are: the one being left stays put and the one arriving is placed ONE
43363
+ // BOX away from it, whichever way the travel goes and however far apart the
43364
+ // two are on the map. So a jump from the first tab to the last is one box of
43365
+ // travel and the tabs in between are never seen flying past — and everything
43366
+ // not in this map is off stage for the duration. Null at rest, when the map
43367
+ // is the whole truth again (see settleTravel).
43368
+ const stageRef = useRef(null);
43369
+ // Which slide the last drawing put on screen: the current one at rest, the
43370
+ // one being travelled TO while the track moves. What the next travel departs
43371
+ // from, because it is what one is looking at.
43372
+ const drawnAreaRef = useRef(undefined);
43373
+ // Which way the travel about to be drawn goes, when whatever asked for it
43374
+ // knows: a window stepping off its last slide comes back on its first, and
43375
+ // only the press says that is a step forward — the map, read between those
43376
+ // two places, says the opposite. Undefined for a travel asked for by name
43377
+ // (--navi-go-to-slide, --navi-back), where the map is the only thing that
43378
+ // knows and is right.
43379
+ const travelStepRef = useRef(null);
43320
43380
  // What to do once the travel now starting is over, handed to the animation as
43321
43381
  // soon as there is one.
43322
43382
  const rollBackRef = useRef(null);
@@ -43418,6 +43478,52 @@ const SlideContainer = ({
43418
43478
  }
43419
43479
  };
43420
43480
 
43481
+ // The travel is over: the stage is struck and every slide goes back where the
43482
+ // map says it is. Nothing is seen moving for it — the slide on screen sits at
43483
+ // the same place whatever the arrangement (its own offset and the track's are
43484
+ // opposites and cancel out), and the others are off stage — as long as the
43485
+ // two are written in one go, which is why this is done here rather than left
43486
+ // to a render.
43487
+ const settleTravel = () => {
43488
+ const track = trackRef.current;
43489
+ if (!track) {
43490
+ return;
43491
+ }
43492
+ const {
43493
+ slideElements,
43494
+ placeOf
43495
+ } = readMap();
43496
+ const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
43497
+ if (!currentElement) {
43498
+ return;
43499
+ }
43500
+ stageRef.current = null;
43501
+ trackAnimationRef.current = null;
43502
+ for (const slideElement of slideElements) {
43503
+ const {
43504
+ x,
43505
+ y
43506
+ } = placeOf.get(readArea(slideElement)) || {
43507
+ x: 0,
43508
+ y: 0
43509
+ };
43510
+ slideElement.style.setProperty("--slide-offset", `${x * 100}% ${y * 100}%`);
43511
+ slideElement.toggleAttribute("data-slide-offstage", slideElement !== currentElement);
43512
+ }
43513
+ const currentArea = readArea(currentElement);
43514
+ drawnAreaRef.current = currentArea;
43515
+ const {
43516
+ x,
43517
+ y
43518
+ } = placeOf.get(currentArea) || {
43519
+ x: 0,
43520
+ y: 0
43521
+ };
43522
+ const offset = `${-x * 100}% ${-y * 100}%`;
43523
+ offsetRef.current = offset;
43524
+ track.style.setProperty("--slide-container-offset", offset);
43525
+ };
43526
+
43421
43527
  // Everything positional is decided here, from the DOM, once per render: where
43422
43528
  // each slide stands on the map, which one is current, and how far the track
43423
43529
  // must be for that one to be the one on screen. Reading the DOM is what makes
@@ -43435,10 +43541,59 @@ const SlideContainer = ({
43435
43541
  // Nothing named, or a name nothing answers to: the first slide is the one
43436
43542
  // shown, the way a stack of pages opens on its first page.
43437
43543
  slideElements[0];
43438
- const currentPlace = placeOf.get(readArea(currentElement)) || {
43544
+ const currentArea = readArea(currentElement);
43545
+ const realPlaceOf = area => placeOf.get(area) || {
43439
43546
  x: 0,
43440
43547
  y: 0
43441
43548
  };
43549
+ const durationMs = durationToMs(duration);
43550
+ // Nothing is travelling, so nothing is staged: the picture to paint is the
43551
+ // map itself, and a stage left over from a travel that has just been given
43552
+ // up on would be painted instead of it.
43553
+ if (noTravel) {
43554
+ stageRef.current = null;
43555
+ }
43556
+ let stage = stageRef.current;
43557
+ const drawnArea = stage ? stage.area : drawnAreaRef.current;
43558
+ const travelStarts = !noTravel && durationMs > 0 && drawnArea !== undefined && drawnArea !== currentArea && slideElements.some(slideElement => readArea(slideElement) === drawnArea);
43559
+ if (travelStarts) {
43560
+ // Where the slide being left IS — the place the stage gave it if a travel
43561
+ // was already playing (a press landing mid-travel departs from where the
43562
+ // eye is, not from the map), its own place otherwise.
43563
+ const departurePlace = stage ? stage.placeByArea.get(drawnArea) ?? realPlaceOf(drawnArea) : realPlaceOf(drawnArea);
43564
+ const step = travelStepRef.current || {
43565
+ x: Math.sign(realPlaceOf(currentArea).x - realPlaceOf(drawnArea).x),
43566
+ y: Math.sign(realPlaceOf(currentArea).y - realPlaceOf(drawnArea).y)
43567
+ };
43568
+ // Kept, not replaced: the slides a chain of quick presses has already
43569
+ // left behind are still trailing off screen, and taking them off stage
43570
+ // now would blink them out mid-travel.
43571
+ const placeByArea = new Map(stage?.placeByArea);
43572
+ placeByArea.set(drawnArea, departurePlace);
43573
+ const arrivalPlace = {
43574
+ x: departurePlace.x + step.x,
43575
+ y: departurePlace.y + step.y
43576
+ };
43577
+ // The cell the arriving slide takes, taken back from whoever was left
43578
+ // standing on it: a travel turned around mid-flight comes back over
43579
+ // ground it has just covered, and the slide it left there would be
43580
+ // underneath the one arriving — two pictures in one box.
43581
+ for (const [stagedArea, stagedPlace] of placeByArea) {
43582
+ if (stagedArea !== drawnArea && stagedArea !== currentArea && stagedPlace.x === arrivalPlace.x && stagedPlace.y === arrivalPlace.y) {
43583
+ placeByArea.delete(stagedArea);
43584
+ }
43585
+ }
43586
+ placeByArea.set(currentArea, arrivalPlace);
43587
+ stage = stageRef.current = {
43588
+ placeByArea,
43589
+ area: currentArea
43590
+ };
43591
+ }
43592
+ // Said about the travel now being drawn and about no other: a re-render in
43593
+ // the middle of one reads the stage back, which already knows.
43594
+ travelStepRef.current = null;
43595
+ const placeOfArea = area => stage && stage.placeByArea.get(area) || realPlaceOf(area);
43596
+ const currentPlace = placeOfArea(currentArea);
43442
43597
  // A transfer waiting for a travel that never happened — a controlled
43443
43598
  // `current` the caller chose not to move: dropped, the focus has no
43444
43599
  // business going anywhere. The one for a travel that DID happen stays,
@@ -43454,17 +43609,18 @@ const SlideContainer = ({
43454
43609
  // and that one knows what was pressed.
43455
43610
  const focusIsLeaving = !focusHandOverRef.current && slideElements.some(slideElement => slideElement !== currentElement && slideElement.contains(document.activeElement));
43456
43611
  for (const slideElement of slideElements) {
43612
+ const area = readArea(slideElement);
43457
43613
  const {
43458
43614
  x,
43459
43615
  y
43460
- } = placeOf.get(readArea(slideElement)) || {
43461
- x: 0,
43462
- y: 0
43463
- };
43616
+ } = placeOfArea(area);
43464
43617
  slideElement.style.setProperty("--slide-offset", `${x * 100}% ${y * 100}%`);
43465
43618
  const isCurrent = slideElement === currentElement;
43466
43619
  slideElement.toggleAttribute("data-current", isCurrent);
43467
43620
  slideElement.toggleAttribute("data-slide-displaced", !isCurrent);
43621
+ // On stage: the two ends of the travel while there is one, and the slide
43622
+ // being shown when there is not.
43623
+ slideElement.toggleAttribute("data-slide-offstage", stage ? !stage.placeByArea.has(area) : !isCurrent);
43468
43624
  if (isCurrent) {
43469
43625
  // Reachable again first, so the focus below has somewhere to land: an
43470
43626
  // inert element cannot take it.
@@ -43491,7 +43647,6 @@ const SlideContainer = ({
43491
43647
  // Where the track ends up, always — the animation below only covers the way
43492
43648
  // there, and when it is over this is what holds.
43493
43649
  track.style.setProperty("--slide-container-offset", offset);
43494
- const durationMs = durationToMs(duration);
43495
43650
  const travels = !noTravel && durationMs > 0 && offsetBefore !== undefined && offsetBefore !== offset;
43496
43651
  if (travels) {
43497
43652
  // The time it takes is the distance it has left to cover: a travel picked
@@ -43522,6 +43677,18 @@ const SlideContainer = ({
43522
43677
  if (pendingRollsRef.current.length) {
43523
43678
  hurryTravel(trackAnimationRef.current);
43524
43679
  }
43680
+ // Arrived: the map is the truth again (see settleTravel). Attached before
43681
+ // the window's own roll back just below, so the stage is struck first and
43682
+ // whatever that one renders is drawn from the map.
43683
+ trackAnimationRef.current.finished.then(settleTravel, () => {
43684
+ // cancelled by the next travel — that one carries the stage on
43685
+ });
43686
+ } else if (stage && trackAnimationRef.current?.playState !== "running") {
43687
+ // Staged with nothing left to play: a travel that was drawn and then had
43688
+ // its animation taken away (a duration set to 0, a re-render landing
43689
+ // between the two). Struck at once rather than left standing, since the
43690
+ // thing it was standing for is over.
43691
+ settleTravel();
43525
43692
  }
43526
43693
  // A window waiting for its travel to be over (see goToArea's own loop
43527
43694
  // branch): the animation says when, and says it about the move that just
@@ -43558,6 +43725,13 @@ const SlideContainer = ({
43558
43725
  for (const slideElement of slideElements) {
43559
43726
  slideElement.toggleAttribute("inert", slideElement !== currentElement);
43560
43727
  }
43728
+ // What is on screen now, for the travel after this one to depart from.
43729
+ // While a travel is playing the stage is the one that knows (it holds the
43730
+ // slide being travelled TO, which is what one is looking at), so this is
43731
+ // only ever written at rest.
43732
+ if (!stageRef.current) {
43733
+ drawnAreaRef.current = currentArea;
43734
+ }
43561
43735
  });
43562
43736
 
43563
43737
  /**
@@ -43652,6 +43826,15 @@ const SlideContainer = ({
43652
43826
  ...cameFromRef.current,
43653
43827
  [area]: readArea(currentElement)
43654
43828
  };
43829
+ // Which way this travel is drawn, said by what asked for it rather than
43830
+ // read off the map: a window stepping off its last slide comes back on its
43831
+ // first, and between those two places the map says "all the way back" when
43832
+ // the press said "one forward". Nothing to say when the travel was asked
43833
+ // for by name — there the map is the only one who knows.
43834
+ travelStepRef.current = dx || dy ? {
43835
+ x: Math.sign(dx),
43836
+ y: Math.sign(dy)
43837
+ } : null;
43655
43838
  if (loop) {
43656
43839
  // A window does not change slide, it rolls: the travel plays, and once it
43657
43840
  // is over the window is put back where it rests while whoever owns the