@jsenv/navi 0.29.121 → 0.29.123

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.
@@ -4536,7 +4536,11 @@ const css$11 = /* css */`
4536
4536
  height: auto; /* User agent reset */
4537
4537
  margin: 0;
4538
4538
  padding: 0; /* User agent reset */
4539
- color: revert; /* Do no inherit element color, callout is inside the element it should use document color though */
4539
+ /* The UA's ink for a [popover] element (CanvasText), not the element's:
4540
+ a callout sits inside the element but writes on its own paper. The
4541
+ color keywords are re-declared against that same ink in
4542
+ navi_css_vars.js (.navi_callout). */
4543
+ color: revert;
4540
4544
  font-weight: initial; /* Callout fells disconnected from the element, font weight should be predictible and stable */
4541
4545
  font-size: initial; /* Callout fells disconnected from the element, font size should be predictible and stable */
4542
4546
  line-height: var(
@@ -39650,6 +39654,17 @@ const LinkCurrentIndicator = () => {
39650
39654
  };
39651
39655
  markAsOutsideTextFlow(LinkCurrentIndicator);
39652
39656
 
39657
+ /**
39658
+ * What the container tells what is inside it: which way it travels so a button
39659
+ * can point the right way without being told twice, what a travel handed to a
39660
+ * slide, and the box itself — which is how a way out written inside reads the
39661
+ * same facts a way out written outside reads by id (see useSlideContainer).
39662
+ *
39663
+ * Its own module, tiny on purpose: the hook that reads a container and the
39664
+ * container that fills this in would otherwise have to import each other.
39665
+ */
39666
+ const SlideContainerContext = createContext(null);
39667
+
39653
39668
  /**
39654
39669
  * What a SlideContainer is doing, read from outside it.
39655
39670
  *
@@ -39689,14 +39704,16 @@ markAsOutsideTextFlow(LinkCurrentIndicator);
39689
39704
  // ([data-slide-ways~="right"]).
39690
39705
  const SLIDE_CURRENT_ATTRIBUTE = "data-slide-current";
39691
39706
  const SLIDE_TOWARD_ATTRIBUTE = "data-slide-travel-toward";
39692
- // Where a travel WOULD go right now: there is a slide that way and the one on
39693
- // screen lets go of it.
39707
+ // Every way out that would DO something right now, in the words the commands
39708
+ // use: a direction ("left", "right", "up", "down") when there is a slide that
39709
+ // way, plus "first" and "last" when one is not already standing at that end of
39710
+ // the walk — and, in all cases, the slide on screen letting go.
39694
39711
  const SLIDE_WAYS_ATTRIBUTE = "data-slide-ways";
39695
- // …and where there is a slide that way but the one on screen holds on to the
39696
- // user (preventNav, or a `required` step still unanswered). Two facts, not one:
39697
- // a way out leading nowhere is not there, while a way out being held is there
39698
- // and says no — which is why it stays visible and explainable rather than
39699
- // hidden.
39712
+ // …and the ways out that are there and refused: the slide on screen holds on to
39713
+ // the user that way (preventNav, or a `required` step still unanswered). Two
39714
+ // facts, not one: a way out leading nowhere is not there at all, while a way
39715
+ // out being held IS there and says no — which is why it stays visible and
39716
+ // explainable rather than hidden. A way out is dead in both cases.
39700
39717
  const SLIDE_HELD_ATTRIBUTE = "data-slide-held";
39701
39718
  // …and the one word said out loud, on the box and on every follower of it, when
39702
39719
  // any of the above has actually changed. It carries the whole state as its
@@ -39704,7 +39721,11 @@ const SLIDE_HELD_ATTRIBUTE = "data-slide-held";
39704
39721
  const SLIDE_STATE_EVENT = "navi_slide_state";
39705
39722
 
39706
39723
  const NO_WAYS = [];
39724
+ // No box to read: not "a box with nothing in it". The difference is the whole
39725
+ // point — see `can` below, which must not answer "there is nowhere to go" to
39726
+ // the question "where can one go" when it has not been told anything yet.
39707
39727
  const NOTHING = {
39728
+ known: false,
39708
39729
  current: undefined,
39709
39730
  toward: undefined,
39710
39731
  areas: NO_WAYS,
@@ -39718,11 +39739,13 @@ const wordsOf = (element, attribute) => {
39718
39739
  };
39719
39740
 
39720
39741
  const readSlideContainerState = (element) => ({
39742
+ known: true,
39721
39743
  current: element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ?? undefined,
39722
39744
  toward: element.getAttribute(SLIDE_TOWARD_ATTRIBUTE) ?? undefined,
39723
- // In DOM order, which is the order of the walk for a line and the order the
39724
- // areas were written in for a map the same order everything else reads
39725
- // (see readMap).
39745
+ // In DOM order which is the order of the WALK for a line, and not
39746
+ // necessarily for a map: there the order is the one the areas are written in
39747
+ // (see parseAreas). Ask `can("first")` / `can("last")` about the ends rather
39748
+ // than the two ends of this list.
39726
39749
  areas: Array.from(
39727
39750
  element.querySelectorAll(":scope > [data-slide-track] > [data-slide]"),
39728
39751
  (slideElement) =>
@@ -39733,6 +39756,7 @@ const readSlideContainerState = (element) => ({
39733
39756
  });
39734
39757
 
39735
39758
  const sameSlideContainerState = (a, b) =>
39759
+ a.known === b.known &&
39736
39760
  a.current === b.current &&
39737
39761
  a.toward === b.toward &&
39738
39762
  a.areas.join(" ") === b.areas.join(" ") &&
@@ -39743,33 +39767,50 @@ const sameSlideContainerState = (a, b) =>
39743
39767
  * @param {string|Element|{current: Element}} [target] - the container: its id
39744
39768
  * (the way everything else addresses one), the element, or a ref to it. Not a
39745
39769
  * follower — a follower is painted for CSS to draw with, the box is what holds
39746
- * the walk. Nothing at all is allowed and answers "no container": a component
39747
- * that may or may not be wired to one calls this unconditionally, like every
39748
- * hook.
39770
+ * the walk. Left out, it is the box this is written INSIDE, if any: a way out
39771
+ * reads the same facts on either side of the box, which is the whole point of
39772
+ * there being one answer to "what would this do".
39749
39773
  * @returns {{
39774
+ * known: boolean,
39750
39775
  * current: string|undefined,
39751
39776
  * toward: string|undefined,
39752
39777
  * areas: string[],
39753
- * can: (direction: "left"|"right"|"up"|"down") => boolean,
39754
- * held: (direction: "left"|"right"|"up"|"down") => boolean,
39778
+ * can: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
39779
+ * held: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
39755
39780
  * }} where the box stands. `current` is the slide on screen — the one being
39756
39781
  * travelled TO while a travel plays, because that is what one is looking at;
39757
39782
  * `toward` is the other slide in the frame while the picture is between two,
39758
- * and nothing at rest. `can` is "a travel that way would happen", `held` is
39759
- * "there is a slide that way and this one says no" — a chevron is dead in
39760
- * both cases and only the second is worth explaining.
39783
+ * and nothing at rest. `can` is "asking for it would do something", `held` is
39784
+ * "it is there and this slide says no" — a way out is dead in both cases, and
39785
+ * only the second is worth a word to the reader.
39786
+ * `known` is false until the box has been read: no box was named and none is
39787
+ * above, the id names nothing, or — for one commit — this mounted before the
39788
+ * box had painted. Until then `can` answers YES, because it is the answer that
39789
+ * degrades well: a way out offered for one frame and then taken away is a
39790
+ * button that did nothing once, while the reverse hides every way out of every
39791
+ * box that this cannot see and says nothing about it.
39761
39792
  */
39762
39793
  const useSlideContainer = (target) => {
39763
39794
  const [state, setState] = useState(NOTHING);
39795
+ // The box this is written inside, when nothing names one. Read
39796
+ // unconditionally, like every hook, and used only as a fallback.
39797
+ const containerInContext = useContext(SlideContainerContext);
39798
+ const fallbackRef = containerInContext?.containerRef;
39764
39799
 
39765
39800
  useLayoutEffect(() => {
39801
+ const resolved = target ?? fallbackRef;
39766
39802
  const element =
39767
- typeof target === "string"
39768
- ? document.getElementById(target)
39769
- : target && "current" in target
39770
- ? target.current
39771
- : target;
39803
+ typeof resolved === "string"
39804
+ ? document.getElementById(resolved)
39805
+ : resolved && "current" in resolved
39806
+ ? resolved.current
39807
+ : resolved;
39772
39808
  if (!element) {
39809
+ if (typeof resolved === "string") {
39810
+ console.warn(
39811
+ `useSlideContainer("${resolved}") but no element with that id found`,
39812
+ );
39813
+ }
39773
39814
  setState(NOTHING);
39774
39815
  return undefined;
39775
39816
  }
@@ -39788,14 +39829,15 @@ const useSlideContainer = (target) => {
39788
39829
  return () => {
39789
39830
  element.removeEventListener(SLIDE_STATE_EVENT, read);
39790
39831
  };
39791
- }, [target]);
39832
+ }, [target, fallbackRef]);
39792
39833
 
39793
39834
  return {
39835
+ known: state.known,
39794
39836
  current: state.current,
39795
39837
  toward: state.toward,
39796
39838
  areas: state.areas,
39797
- can: (direction) => state.ways.includes(direction),
39798
- held: (direction) => state.held.includes(direction),
39839
+ can: (wayOut) => !state.known || state.ways.includes(wayOut),
39840
+ held: (wayOut) => state.held.includes(wayOut),
39799
39841
  };
39800
39842
  };
39801
39843
 
@@ -49901,9 +49943,6 @@ const isWayOut = element => Boolean(element && element.closest && element.closes
49901
49943
  // empty map, and a re-render changes nothing for them.
49902
49944
  const EMPTY_VALUE_BY_AREA = {};
49903
49945
 
49904
- // What the container tells what is inside it: which way it travels, so a button
49905
- // can point the right way without being told twice.
49906
- const SlideContainerContext = createContext(null);
49907
49946
  // What a slide tells what is inside IT: whether leaving it is allowed right
49908
49947
  // now, so its own prev/next buttons say so instead of failing when pressed.
49909
49948
  const SlideContext = createContext(null);
@@ -51323,6 +51362,16 @@ const SlideContainer = ({
51323
51362
  const paintWays = currentElement => {
51324
51363
  const ways = [];
51325
51364
  const held = [];
51365
+ // Whether the slide being left says no, this way. The one gate again, read
51366
+ // exactly as goToArea reads it at the moment of the travel — so what is
51367
+ // published here and what would happen cannot disagree.
51368
+ const holdsTowards = forward => currentElement?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
51369
+ const say = (name, offered, forward) => {
51370
+ if (!offered) {
51371
+ return;
51372
+ }
51373
+ (holdsTowards(forward) ? held : ways).push(name);
51374
+ };
51326
51375
  for (const direction of Object.keys(DIRECTIONS)) {
51327
51376
  const {
51328
51377
  dx,
@@ -51333,13 +51382,18 @@ const SlideContainer = ({
51333
51382
  // would be saying it about every row on the page.
51334
51383
  continue;
51335
51384
  }
51336
- if (!areaTowards(dx, dy)) {
51337
- continue;
51338
- }
51339
- const forward = dx > 0 || dy > 0;
51340
- const isHeld = currentElement?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
51341
- (isHeld ? held : ways).push(direction);
51385
+ say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
51342
51386
  }
51387
+ // The two ends are ways out like the others — "all the way that way", said
51388
+ // by the map's own order rather than by a direction (see goToEnd, and
51389
+ // SlideContainer.First / .Last). Offered while one is not already standing
51390
+ // there, and held by the same lock the direction they lie in would be.
51391
+ const {
51392
+ order
51393
+ } = readMap();
51394
+ const currentArea = currentElement ? readArea(currentElement) : undefined;
51395
+ say("first", order.length > 0 && order[0] !== currentArea, false);
51396
+ say("last", order.length > 0 && order[order.length - 1] !== currentArea, true);
51343
51397
  for (const element of travelPainters()) {
51344
51398
  if (ways.length) {
51345
51399
  element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
@@ -52082,7 +52136,11 @@ const SlideContainer = ({
52082
52136
  answeredAreas,
52083
52137
  done,
52084
52138
  valueByArea,
52085
- settleFocus
52139
+ settleFocus,
52140
+ // The box itself, for what is written INSIDE it to read the same
52141
+ // facts what is written outside reads by id (useSlideContainer):
52142
+ // a way out is a way out on either side of the box.
52143
+ containerRef
52086
52144
  },
52087
52145
  children: children
52088
52146
  })
@@ -52295,14 +52353,19 @@ const SlideMove = ({
52295
52353
  label
52296
52354
  } = DIRECTIONS[direction];
52297
52355
  const forward = dx > 0 || dy > 0;
52298
- // The same fact, read from wherever this way out is written. Inside a slide it
52299
- // comes down as context the slide holding the user is this button's
52300
- // ancestor. Written AROUND the box (only slides go in it, so a chevron pinned
52301
- // to the edge of a full-screen surface has to be), it is not, so it is read
52302
- // off the box this button already names to ask for the travel: one prop, and
52303
- // the way out behaves the same on either side of the box.
52356
+ // What this way out would DO, read from wherever it is written: off the box it
52357
+ // names when it is drawn around the box (only slides go in it, so a chevron
52358
+ // pinned to the edge of a full-screen surface has to be), off the box above it
52359
+ // otherwise. One answer on either side of the box.
52304
52360
  const slides = useSlideContainer(rest.commandFor);
52305
- const locked = (forward ? locks?.preventNavNext : locks?.preventNavPrevious) || slides.held(direction);
52361
+ // Two reasons to be dead, and they are not the same thing to a reader: this
52362
+ // slide holding on to them (worth explaining — see readOnly on SlideNavButton)
52363
+ // and there being nothing that way at all (the end of the walk, which explains
52364
+ // itself). The lock also comes down as context when this is written inside a
52365
+ // slide, which is the same fact one commit earlier: the box publishes it after
52366
+ // the render, and there is no frame where the way out is live for nothing.
52367
+ const held = (forward ? locks?.preventNavNext : locks?.preventNavPrevious) || slides.held(direction);
52368
+ const locked = held || !slides.can(direction);
52306
52369
  return jsx(SlideNavButton, {
52307
52370
  command: command,
52308
52371
  locked: locked,
@@ -52314,16 +52377,24 @@ const SlideMove = ({
52314
52377
 
52315
52378
  // "All the way that way": the first slide of the walk, or the last one. Not a
52316
52379
  // direction — a map reads its own order — so it is its own component rather
52317
- // than a fifth arrow.
52380
+ // than a fifth arrow. Which is exactly why it cannot work out on its own whether
52381
+ // it would do anything: only the box knows its order, so only the box can say
52382
+ // that one is already standing at that end. It says it in the same breath as the
52383
+ // rest (see paintWays).
52318
52384
  const SlideEnd = ({
52319
52385
  last,
52320
52386
  ...rest
52321
- }) => jsx(SlideNavButton, {
52322
- command: last ? "--navi-last" : "--navi-first",
52323
- ChevronSvg: last ? ChevronLastSvg : ChevronFirstSvg,
52324
- "aria-label": last ? "Last slide" : "First slide",
52325
- ...rest
52326
- });
52387
+ }) => {
52388
+ const slides = useSlideContainer(rest.commandFor);
52389
+ const wayOut = last ? "last" : "first";
52390
+ return jsx(SlideNavButton, {
52391
+ command: last ? "--navi-last" : "--navi-first",
52392
+ locked: slides.held(wayOut) || !slides.can(wayOut),
52393
+ ChevronSvg: last ? ChevronLastSvg : ChevronFirstSvg,
52394
+ "aria-label": last ? "Last slide" : "First slide",
52395
+ ...rest
52396
+ });
52397
+ };
52327
52398
  const SlideFirst = props => jsx(SlideEnd, {
52328
52399
  ...props,
52329
52400
  last: false