@jsenv/navi 0.29.120 → 0.29.122

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.
@@ -4256,6 +4256,53 @@ const findControlProxy = (el) => {
4256
4256
  return firstProxy;
4257
4257
  };
4258
4258
 
4259
+ /**
4260
+ * Typography reset shared by the surfaces a control opens: callout, popover,
4261
+ * dialog.
4262
+ *
4263
+ * A surface is painted on top of the page but lives in the DOM subtree of the
4264
+ * element it opens from, so every inherited text property of that element
4265
+ * reaches it. The ink an element chose for its own background — centered,
4266
+ * shadowed, uppercase, letter-spaced, kept on one line — arrives on paper that
4267
+ * has none of that background, and the caller discovers it as a symptom
4268
+ * (a blurred tooltip, a centered message) with nothing on screen pointing back
4269
+ * to the rule three elements up. A surface writes its own text, on its own
4270
+ * paper, in the document's terms.
4271
+ *
4272
+ * Every one of them is set to its initial value rather than `revert`: only
4273
+ * `color` and `background-color` are declared by the UA on a `[popover]`/
4274
+ * `<dialog>`, so `revert` on any of these would roll back to the inherited
4275
+ * value — the very thing to stop. `text-align: initial` is `start`, which
4276
+ * still reads the surface's own `direction`, so an RTL document keeps its
4277
+ * side.
4278
+ *
4279
+ * `font-family` is deliberately absent: a surface keeps the face of what
4280
+ * opened it, so a section written in a display font gets its tooltips in that
4281
+ * font. `color`, `font-size` and `font-weight` are absent too — each surface
4282
+ * answers those its own way (a popup writes in --navi-popup-color and follows
4283
+ * the size of what opened it; a callout reverts to the document's ink and
4284
+ * size).
4285
+ *
4286
+ * In `@layer navi` so an app that wants one of these back says so on the
4287
+ * surface and wins, without having to out-specify anything.
4288
+ */
4289
+ const surfaceTextCss = /* css */ `
4290
+ @layer navi {
4291
+ .navi_callout,
4292
+ .navi_popover,
4293
+ .navi_dialog {
4294
+ font-style: initial;
4295
+ text-align: initial;
4296
+ text-indent: initial;
4297
+ text-transform: initial;
4298
+ text-shadow: none;
4299
+ white-space: initial;
4300
+ word-spacing: initial;
4301
+ letter-spacing: initial;
4302
+ }
4303
+ }
4304
+ `;
4305
+
4259
4306
  const CalloutContext = createContext();
4260
4307
  const useCalloutRequestClose = () => {
4261
4308
  return useContext(CalloutContext)?.requestClose;
@@ -4601,7 +4648,6 @@ const css$11 = /* css */`
4601
4648
  box-sizing: border-box;
4602
4649
  box-decoration-break: clone;
4603
4650
  align-self: center;
4604
- white-space: normal; /* Override in case ancetor sets nowrap */
4605
4651
  word-break: break-word;
4606
4652
  overflow-wrap: anywhere;
4607
4653
 
@@ -4670,6 +4716,8 @@ const css$11 = /* css */`
4670
4716
  }
4671
4717
  }
4672
4718
  }
4719
+
4720
+ ${surfaceTextCss}
4673
4721
  `;
4674
4722
 
4675
4723
  /**
@@ -39602,6 +39650,193 @@ const LinkCurrentIndicator = () => {
39602
39650
  };
39603
39651
  markAsOutsideTextFlow(LinkCurrentIndicator);
39604
39652
 
39653
+ /**
39654
+ * What the container tells what is inside it: which way it travels so a button
39655
+ * can point the right way without being told twice, what a travel handed to a
39656
+ * slide, and the box itself — which is how a way out written inside reads the
39657
+ * same facts a way out written outside reads by id (see useSlideContainer).
39658
+ *
39659
+ * Its own module, tiny on purpose: the hook that reads a container and the
39660
+ * container that fills this in would otherwise have to import each other.
39661
+ */
39662
+ const SlideContainerContext = createContext(null);
39663
+
39664
+ /**
39665
+ * What a SlideContainer is doing, read from outside it.
39666
+ *
39667
+ * Only slides go in the box, so everything drawn AROUND the travel is written
39668
+ * around it — a chevron pinned to the edge of a full-screen viewer, a "3 / 8"
39669
+ * counter, a bar. Those need to know things the box alone knows: where one is,
39670
+ * and whether there is anywhere to go that way. A container driven by nobody
39671
+ * (no `current`, no `signal`) knows them and no one else does, so this is also
39672
+ * what keeps such a container usable without lifting its state out of it.
39673
+ *
39674
+ * Two channels, and they answer two different questions. The attributes the box
39675
+ * paints on itself are the STATE: they are there for whoever arrives later, and
39676
+ * CSS draws with them without any of this ([data-slide-ways~="right"]). The
39677
+ * event it dispatches is the NEWS: it says WHEN something changed, which no
39678
+ * attribute can say. So this reads the first and listens to the second.
39679
+ *
39680
+ * Not watching the DOM for it, which is the tempting third way and the wrong
39681
+ * one: a write is not a change. Under a finger the box writes where the picture
39682
+ * leans on every frame, with the same value in it more often than not, and a
39683
+ * watcher is woken for every one of them — while the box itself knows perfectly
39684
+ * well whether anything is different, and says so once.
39685
+ *
39686
+ * Nothing is told to the box in return, and nothing has to be wired: it
39687
+ * announces to itself and to its followers, so the thing doing the reading sits
39688
+ * anywhere on the page — in a fixed bar, in the frame around the box, in a
39689
+ * dialog holding it — and a container which never re-renders (a travel it drove
39690
+ * itself) is still followed.
39691
+ *
39692
+ * Reading only. Asking for a travel is what it always was: a command aimed at
39693
+ * the box (`commandFor={id}` + `--navi-right`, or triggerNaviCommand) — one way
39694
+ * of asking, wherever the asking is written.
39695
+ */
39696
+
39697
+
39698
+ // The vocabulary the box publishes, in one place: written by slide_container,
39699
+ // read here and by anything styling in CSS alone
39700
+ // ([data-slide-ways~="right"]).
39701
+ const SLIDE_CURRENT_ATTRIBUTE = "data-slide-current";
39702
+ const SLIDE_TOWARD_ATTRIBUTE = "data-slide-travel-toward";
39703
+ // Every way out that would DO something right now, in the words the commands
39704
+ // use: a direction ("left", "right", "up", "down") when there is a slide that
39705
+ // way, plus "first" and "last" when one is not already standing at that end of
39706
+ // the walk — and, in all cases, the slide on screen letting go.
39707
+ const SLIDE_WAYS_ATTRIBUTE = "data-slide-ways";
39708
+ // …and the ways out that are there and refused: the slide on screen holds on to
39709
+ // the user that way (preventNav, or a `required` step still unanswered). Two
39710
+ // facts, not one: a way out leading nowhere is not there at all, while a way
39711
+ // out being held IS there and says no — which is why it stays visible and
39712
+ // explainable rather than hidden. A way out is dead in both cases.
39713
+ const SLIDE_HELD_ATTRIBUTE = "data-slide-held";
39714
+ // …and the one word said out loud, on the box and on every follower of it, when
39715
+ // any of the above has actually changed. It carries the whole state as its
39716
+ // detail, so `onnavi_slide_state` on the box is a way of hearing it too.
39717
+ const SLIDE_STATE_EVENT = "navi_slide_state";
39718
+
39719
+ const NO_WAYS = [];
39720
+ // No box to read: not "a box with nothing in it". The difference is the whole
39721
+ // point — see `can` below, which must not answer "there is nowhere to go" to
39722
+ // the question "where can one go" when it has not been told anything yet.
39723
+ const NOTHING = {
39724
+ known: false,
39725
+ current: undefined,
39726
+ toward: undefined,
39727
+ areas: NO_WAYS,
39728
+ ways: NO_WAYS,
39729
+ held: NO_WAYS,
39730
+ };
39731
+
39732
+ const wordsOf = (element, attribute) => {
39733
+ const value = element.getAttribute(attribute);
39734
+ return value ? value.split(" ") : NO_WAYS;
39735
+ };
39736
+
39737
+ const readSlideContainerState = (element) => ({
39738
+ known: true,
39739
+ current: element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ?? undefined,
39740
+ toward: element.getAttribute(SLIDE_TOWARD_ATTRIBUTE) ?? undefined,
39741
+ // In DOM order — which is the order of the WALK for a line, and not
39742
+ // necessarily for a map: there the order is the one the areas are written in
39743
+ // (see parseAreas). Ask `can("first")` / `can("last")` about the ends rather
39744
+ // than the two ends of this list.
39745
+ areas: Array.from(
39746
+ element.querySelectorAll(":scope > [data-slide-track] > [data-slide]"),
39747
+ (slideElement) =>
39748
+ slideElement.getAttribute("data-slide-area") || slideElement.id || "",
39749
+ ),
39750
+ ways: wordsOf(element, SLIDE_WAYS_ATTRIBUTE),
39751
+ held: wordsOf(element, SLIDE_HELD_ATTRIBUTE),
39752
+ });
39753
+
39754
+ const sameSlideContainerState = (a, b) =>
39755
+ a.known === b.known &&
39756
+ a.current === b.current &&
39757
+ a.toward === b.toward &&
39758
+ a.areas.join(" ") === b.areas.join(" ") &&
39759
+ a.ways.join(" ") === b.ways.join(" ") &&
39760
+ a.held.join(" ") === b.held.join(" ");
39761
+
39762
+ /**
39763
+ * @param {string|Element|{current: Element}} [target] - the container: its id
39764
+ * (the way everything else addresses one), the element, or a ref to it. Not a
39765
+ * follower — a follower is painted for CSS to draw with, the box is what holds
39766
+ * the walk. Left out, it is the box this is written INSIDE, if any: a way out
39767
+ * reads the same facts on either side of the box, which is the whole point of
39768
+ * there being one answer to "what would this do".
39769
+ * @returns {{
39770
+ * known: boolean,
39771
+ * current: string|undefined,
39772
+ * toward: string|undefined,
39773
+ * areas: string[],
39774
+ * can: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
39775
+ * held: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
39776
+ * }} where the box stands. `current` is the slide on screen — the one being
39777
+ * travelled TO while a travel plays, because that is what one is looking at;
39778
+ * `toward` is the other slide in the frame while the picture is between two,
39779
+ * and nothing at rest. `can` is "asking for it would do something", `held` is
39780
+ * "it is there and this slide says no" — a way out is dead in both cases, and
39781
+ * only the second is worth a word to the reader.
39782
+ * `known` is false until the box has been read: no box was named and none is
39783
+ * above, the id names nothing, or — for one commit — this mounted before the
39784
+ * box had painted. Until then `can` answers YES, because it is the answer that
39785
+ * degrades well: a way out offered for one frame and then taken away is a
39786
+ * button that did nothing once, while the reverse hides every way out of every
39787
+ * box that this cannot see and says nothing about it.
39788
+ */
39789
+ const useSlideContainer = (target) => {
39790
+ const [state, setState] = useState(NOTHING);
39791
+ // The box this is written inside, when nothing names one. Read
39792
+ // unconditionally, like every hook, and used only as a fallback.
39793
+ const containerInContext = useContext(SlideContainerContext);
39794
+ const fallbackRef = containerInContext?.containerRef;
39795
+
39796
+ useLayoutEffect(() => {
39797
+ const resolved = target ?? fallbackRef;
39798
+ const element =
39799
+ typeof resolved === "string"
39800
+ ? document.getElementById(resolved)
39801
+ : resolved && "current" in resolved
39802
+ ? resolved.current
39803
+ : resolved;
39804
+ if (!element) {
39805
+ if (typeof resolved === "string") {
39806
+ console.warn(
39807
+ `useSlideContainer("${resolved}") but no element with that id found`,
39808
+ );
39809
+ }
39810
+ setState(NOTHING);
39811
+ return undefined;
39812
+ }
39813
+ // Read off the DOM rather than taken from the event's detail, even though
39814
+ // the two say the same thing: the first read has no event to take it from
39815
+ // (the box was already standing somewhere when this mounted), and one way
39816
+ // of reading is one thing that can be wrong.
39817
+ const read = () => {
39818
+ const nextState = readSlideContainerState(element);
39819
+ setState((previous) =>
39820
+ sameSlideContainerState(previous, nextState) ? previous : nextState,
39821
+ );
39822
+ };
39823
+ read();
39824
+ element.addEventListener(SLIDE_STATE_EVENT, read);
39825
+ return () => {
39826
+ element.removeEventListener(SLIDE_STATE_EVENT, read);
39827
+ };
39828
+ }, [target, fallbackRef]);
39829
+
39830
+ return {
39831
+ known: state.known,
39832
+ current: state.current,
39833
+ toward: state.toward,
39834
+ areas: state.areas,
39835
+ can: (wayOut) => !state.known || state.ways.includes(wayOut),
39836
+ held: (wayOut) => state.held.includes(wayOut),
39837
+ };
39838
+ };
39839
+
39605
39840
  installImportMetaCssBuild(import.meta);/**
39606
39841
  * TabList component with support for horizontal and vertical layouts
39607
39842
  * https://dribbble.com/search/tabs
@@ -40000,19 +40235,17 @@ const Nav = ({
40000
40235
  }
40001
40236
  slideContainerElementRef.current = containerElement;
40002
40237
  const readContainer = () => {
40003
- setCurrentSlideArea(containerElement.getAttribute("data-slide-current") ?? undefined);
40238
+ setCurrentSlideArea(containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ?? undefined);
40004
40239
  paintIndicatorGeometryRef.current();
40005
40240
  };
40006
40241
  readContainer();
40007
- // The container says where one is and what the picture leans on, and says
40008
- // it in the DOM: nothing here is told, everything is read which is what
40009
- // lets this row sit anywhere on the page (above the box, in a fixed bar)
40010
- // rather than inside it.
40011
- const attributeObserver = new MutationObserver(readContainer);
40012
- attributeObserver.observe(containerElement, {
40013
- attributes: true,
40014
- attributeFilter: ["data-slide-current", "data-slide-travel-toward"]
40015
- });
40242
+ // Where one is and what the picture leans on are written on the container,
40243
+ // and the container says out loud when either has changed: this row is not
40244
+ // in the box (it sits above it, in a fixed bar, anywhere), so it reads the
40245
+ // first and listens to the second. Listening rather than watching the DOM
40246
+ // because a write is not a change — the attribute the trait follows is
40247
+ // written on every frame of a gesture with the same value in it.
40248
+ containerElement.addEventListener(SLIDE_STATE_EVENT, readContainer);
40016
40249
  // A row whose tabs changed width — a badge count, a font that just
40017
40250
  // arrived, a window resized — is measured again: what was written is
40018
40251
  // pixels, and pixels go stale.
@@ -40021,7 +40254,7 @@ const Nav = ({
40021
40254
  });
40022
40255
  sizeObserver.observe(navRef.current);
40023
40256
  return () => {
40024
- attributeObserver.disconnect();
40257
+ containerElement.removeEventListener(SLIDE_STATE_EVENT, readContainer);
40025
40258
  sizeObserver.disconnect();
40026
40259
  slideContainerElementRef.current = null;
40027
40260
  };
@@ -49706,9 +49939,6 @@ const isWayOut = element => Boolean(element && element.closest && element.closes
49706
49939
  // empty map, and a re-render changes nothing for them.
49707
49940
  const EMPTY_VALUE_BY_AREA = {};
49708
49941
 
49709
- // What the container tells what is inside it: which way it travels, so a button
49710
- // can point the right way without being told twice.
49711
- const SlideContainerContext = createContext(null);
49712
49942
  // What a slide tells what is inside IT: whether leaving it is allowed right
49713
49943
  // now, so its own prev/next buttons say so instead of failing when pressed.
49714
49944
  const SlideContext = createContext(null);
@@ -49916,6 +50146,13 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
49916
50146
  * screens are steps rather than places — so the keys keep the meaning the
49917
50147
  * content gives them, and travelling stays something one asks for (a button,
49918
50148
  * a command).
50149
+ * WHERE they are heard is a separate question, and it is not answered here: a
50150
+ * key only ever reaches what has the focus, so this box hears the ones pressed
50151
+ * inside it — and every follower of it hears the rest. A chevron pinned to the
50152
+ * edge of a full-screen surface is outside the box by necessity (only slides
50153
+ * go in it), so the surface says `data-slide-container-follows={id}` and the
50154
+ * arrows keep walking wherever the keyboard is in it. See the paragraph above
50155
+ * about what is drawn AROUND the travel.
49919
50156
  * @param {boolean|"x"|"y"|"xy"} [props.travelByDrag=true] - whether a pointer
49920
50157
  * dragging the slides travels. On by default: slides side by side are
49921
50158
  * something one expects to push around with a thumb. Off where the gesture
@@ -50236,7 +50473,7 @@ const SlideContainer = ({
50236
50473
  // there is nothing else for it to read — a slide the container holds by
50237
50474
  // itself is known to no one else.
50238
50475
  const paintCurrentArea = area => {
50239
- containerRef.current?.setAttribute("data-slide-current", area);
50476
+ containerRef.current?.setAttribute(SLIDE_CURRENT_ATTRIBUTE, area);
50240
50477
  };
50241
50478
  const markAnswered = area => {
50242
50479
  const order = readMap().slideElements.map(readArea);
@@ -50559,6 +50796,14 @@ const SlideContainer = ({
50559
50796
  if (!stageRef.current) {
50560
50797
  drawnAreaRef.current = currentArea;
50561
50798
  }
50799
+ // Said last, and after data-current has been written: what a travel would do
50800
+ // is read off the map from the slide that is now current, and off the locks
50801
+ // that slide is wearing in this very commit.
50802
+ paintWays(currentElement);
50803
+ // …and once everything this commit had to write is written, the box says so
50804
+ // — a single announcement for a render that may have changed several of the
50805
+ // facts at once.
50806
+ announceState();
50562
50807
  // The finger has the last word: everything above drew the map at rest, and
50563
50808
  // where the track actually is right now is where the gesture put it.
50564
50809
  paintDrag();
@@ -51086,11 +51331,109 @@ const SlideContainer = ({
51086
51331
  const paintTravelToward = area => {
51087
51332
  for (const element of travelPainters()) {
51088
51333
  if (area) {
51089
- element.setAttribute("data-slide-travel-toward", area);
51334
+ element.setAttribute(SLIDE_TOWARD_ATTRIBUTE, area);
51090
51335
  } else {
51091
- element.removeAttribute("data-slide-travel-toward");
51336
+ element.removeAttribute(SLIDE_TOWARD_ATTRIBUTE);
51092
51337
  }
51093
51338
  }
51339
+ // The one fact that changes without a render behind it: under a finger this
51340
+ // is written per frame, so the announcement is left to say whether any of
51341
+ // those writes was a change.
51342
+ announceState();
51343
+ };
51344
+
51345
+ // What a travel would do right now, said in two lists because they are two
51346
+ // different facts: `ways` is where one WOULD go — there is a slide that way
51347
+ // and the one on screen lets go of it — and `held` is where there is a slide
51348
+ // that way and this one holds on to the user (preventNav, a `required` step
51349
+ // still unanswered). A way out leading nowhere is not there; a way out being
51350
+ // held is there and says no, which is why it stays visible and explainable.
51351
+ //
51352
+ // Painted rather than rendered, and on the followers too: only slides go in
51353
+ // this box, so the chevrons, the counters and the bars that go with it are
51354
+ // written AROUND it — in CSS off these attributes
51355
+ // ([data-slide-ways~="right"]), or through useSlideContainer, which watches
51356
+ // them. Nothing is told, everything is read: it is what lets them sit
51357
+ // anywhere, and what lets a container nobody drives still be followed.
51358
+ const paintWays = currentElement => {
51359
+ const ways = [];
51360
+ const held = [];
51361
+ // Whether the slide being left says no, this way. The one gate again, read
51362
+ // exactly as goToArea reads it at the moment of the travel — so what is
51363
+ // published here and what would happen cannot disagree.
51364
+ const holdsTowards = forward => currentElement?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
51365
+ const say = (name, offered, forward) => {
51366
+ if (!offered) {
51367
+ return;
51368
+ }
51369
+ (holdsTowards(forward) ? held : ways).push(name);
51370
+ };
51371
+ for (const direction of Object.keys(DIRECTIONS)) {
51372
+ const {
51373
+ dx,
51374
+ dy
51375
+ } = DIRECTIONS[direction];
51376
+ if (!mapAxes?.includes(dx ? "x" : "y")) {
51377
+ // Not an axis this map has: a row has no up and no down, and saying so
51378
+ // would be saying it about every row on the page.
51379
+ continue;
51380
+ }
51381
+ say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
51382
+ }
51383
+ // The two ends are ways out like the others — "all the way that way", said
51384
+ // by the map's own order rather than by a direction (see goToEnd, and
51385
+ // SlideContainer.First / .Last). Offered while one is not already standing
51386
+ // there, and held by the same lock the direction they lie in would be.
51387
+ const {
51388
+ order
51389
+ } = readMap();
51390
+ const currentArea = currentElement ? readArea(currentElement) : undefined;
51391
+ say("first", order.length > 0 && order[0] !== currentArea, false);
51392
+ say("last", order.length > 0 && order[order.length - 1] !== currentArea, true);
51393
+ for (const element of travelPainters()) {
51394
+ if (ways.length) {
51395
+ element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
51396
+ } else {
51397
+ element.removeAttribute(SLIDE_WAYS_ATTRIBUTE);
51398
+ }
51399
+ if (held.length) {
51400
+ element.setAttribute(SLIDE_HELD_ATTRIBUTE, held.join(" "));
51401
+ } else {
51402
+ element.removeAttribute(SLIDE_HELD_ATTRIBUTE);
51403
+ }
51404
+ }
51405
+ };
51406
+
51407
+ // The news, as opposed to the state. Everything painted above IS the state:
51408
+ // it is what anything arriving later reads, and what CSS draws with. What an
51409
+ // attribute cannot say is WHEN it changed — and watching it is the wrong way
51410
+ // to ask, because a write is not a change: `data-slide-travel-toward` is
51411
+ // written on every frame of a gesture with the same value in it, and every
51412
+ // watcher would be woken sixty times a second for nothing. So the box says it
51413
+ // itself, once, and only when something is actually different.
51414
+ //
51415
+ // Said to its followers as well as to itself, exactly as the painting is: a
51416
+ // frame drawn around the box hears what the box is doing without knowing its
51417
+ // id, and a row of tabs beside it listens on the box by id. Not bubbling, like
51418
+ // every other navi event — what is announced is about THIS box, and a box
51419
+ // inside another must not be heard as the one around it.
51420
+ const announcedRef = useRef(null);
51421
+ const announceState = () => {
51422
+ const containerEl = containerRef.current;
51423
+ if (!containerEl) {
51424
+ return;
51425
+ }
51426
+ const state = readSlideContainerState(containerEl);
51427
+ const announced = announcedRef.current;
51428
+ if (announced && sameSlideContainerState(announced, state)) {
51429
+ return;
51430
+ }
51431
+ announcedRef.current = state;
51432
+ for (const element of travelPainters()) {
51433
+ dispatchCustomEvent(element, SLIDE_STATE_EVENT, {
51434
+ ...state
51435
+ });
51436
+ }
51094
51437
  };
51095
51438
 
51096
51439
  // Where the picture stands relative to the slide that is CURRENT, in boxes:
@@ -51789,7 +52132,11 @@ const SlideContainer = ({
51789
52132
  answeredAreas,
51790
52133
  done,
51791
52134
  valueByArea,
51792
- settleFocus
52135
+ settleFocus,
52136
+ // The box itself, for what is written INSIDE it to read the same
52137
+ // facts what is written outside reads by id (useSlideContainer):
52138
+ // a way out is a way out on either side of the box.
52139
+ containerRef
51793
52140
  },
51794
52141
  children: children
51795
52142
  })
@@ -52002,7 +52349,19 @@ const SlideMove = ({
52002
52349
  label
52003
52350
  } = DIRECTIONS[direction];
52004
52351
  const forward = dx > 0 || dy > 0;
52005
- const locked = forward ? locks?.preventNavNext : locks?.preventNavPrevious;
52352
+ // What this way out would DO, read from wherever it is written: off the box it
52353
+ // names when it is drawn around the box (only slides go in it, so a chevron
52354
+ // pinned to the edge of a full-screen surface has to be), off the box above it
52355
+ // otherwise. One answer on either side of the box.
52356
+ const slides = useSlideContainer(rest.commandFor);
52357
+ // Two reasons to be dead, and they are not the same thing to a reader: this
52358
+ // slide holding on to them (worth explaining — see readOnly on SlideNavButton)
52359
+ // and there being nothing that way at all (the end of the walk, which explains
52360
+ // itself). The lock also comes down as context when this is written inside a
52361
+ // slide, which is the same fact one commit earlier: the box publishes it after
52362
+ // the render, and there is no frame where the way out is live for nothing.
52363
+ const held = (forward ? locks?.preventNavNext : locks?.preventNavPrevious) || slides.held(direction);
52364
+ const locked = held || !slides.can(direction);
52006
52365
  return jsx(SlideNavButton, {
52007
52366
  command: command,
52008
52367
  locked: locked,
@@ -52014,16 +52373,24 @@ const SlideMove = ({
52014
52373
 
52015
52374
  // "All the way that way": the first slide of the walk, or the last one. Not a
52016
52375
  // direction — a map reads its own order — so it is its own component rather
52017
- // than a fifth arrow.
52376
+ // than a fifth arrow. Which is exactly why it cannot work out on its own whether
52377
+ // it would do anything: only the box knows its order, so only the box can say
52378
+ // that one is already standing at that end. It says it in the same breath as the
52379
+ // rest (see paintWays).
52018
52380
  const SlideEnd = ({
52019
52381
  last,
52020
52382
  ...rest
52021
- }) => jsx(SlideNavButton, {
52022
- command: last ? "--navi-last" : "--navi-first",
52023
- ChevronSvg: last ? ChevronLastSvg : ChevronFirstSvg,
52024
- "aria-label": last ? "Last slide" : "First slide",
52025
- ...rest
52026
- });
52383
+ }) => {
52384
+ const slides = useSlideContainer(rest.commandFor);
52385
+ const wayOut = last ? "last" : "first";
52386
+ return jsx(SlideNavButton, {
52387
+ command: last ? "--navi-last" : "--navi-first",
52388
+ locked: slides.held(wayOut) || !slides.can(wayOut),
52389
+ ChevronSvg: last ? ChevronLastSvg : ChevronFirstSvg,
52390
+ "aria-label": last ? "Last slide" : "First slide",
52391
+ ...rest
52392
+ });
52393
+ };
52027
52394
  const SlideFirst = props => jsx(SlideEnd, {
52028
52395
  ...props,
52029
52396
  last: false
@@ -54555,6 +54922,7 @@ const css$E = /* css */`
54555
54922
  }
54556
54923
  }
54557
54924
 
54925
+ ${surfaceTextCss}
54558
54926
  ${popupCss}
54559
54927
  `;
54560
54928
 
@@ -56052,6 +56420,7 @@ const css$D = /* css */`
56052
56420
  }
56053
56421
  }
56054
56422
 
56423
+ ${surfaceTextCss}
56055
56424
  ${popupCss}
56056
56425
  `;
56057
56426
 
@@ -66172,6 +66541,20 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`
66172
66541
  pointer-events: none;
66173
66542
  user-select: none;
66174
66543
 
66544
+ /* The value the picker draws itself is the control's own text, at the
66545
+ control's font size: it keeps the control line, snapped to the pixel
66546
+ like the field around it. A caller's "ui" is the caller's own drawing
66547
+ of the control, and it is written on the page's line, as the number,
66548
+ so each text it holds keeps a line relative to its own size. The
66549
+ control line is a length (--navi-control-line-height), and inherited
66550
+ it would arrive as the control's pixels: a 14px label under an 18px
66551
+ picker's 23px line carries ~5px of leading above and below that no
66552
+ glyph occupies and nothing on screen explains. Same reason a popup
66553
+ takes the number — see .navi_popup in popup.jsx. */
66554
+ &[data-picker-facade] {
66555
+ line-height: var(--navi-line-height);
66556
+ }
66557
+
66175
66558
  &[navi-placeholder] {
66176
66559
  color: var(--picker-placeholder-color);
66177
66560
  font-style: var(--picker-placeholder-font-style);
@@ -66311,6 +66694,16 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`
66311
66694
  --x-corner-bottom-left-radius: initial;
66312
66695
 
66313
66696
  display: contents;
66697
+ /* What opens from a control is a page of its own, not part of that
66698
+ control's type scale: it is written at the control font by name,
66699
+ rather than by inheriting the size the picker itself is drawn at. A
66700
+ caller who sizes a picker to the façade it holds (so the padding, the
66701
+ corners and the chevron the picker draws in em land on the text the
66702
+ caller actually wrote) would otherwise take the popup down with it:
66703
+ its title, and everything in it that does not state a size of its
66704
+ own. Same reason the popup is written on the page's line rather than
66705
+ the control's — see .navi_popup in popup.jsx. */
66706
+ font-size: var(--navi-control-font-size);
66314
66707
  text-align: initial; /* Don't inherit picker text align */
66315
66708
  }
66316
66709
 
@@ -66714,6 +67107,12 @@ const PickerButton = props => {
66714
67107
  }
66715
67108
  }), variant === "headless" || ui === "default" ? null : jsx(Text, {
66716
67109
  className: "navi_picker_value"
67110
+ // Tells the caller's own drawing of the control from the value
67111
+ // the picker draws itself, so each is written on its own line
67112
+ // (see .navi_picker_value in the CSS above).
67113
+ ,
67114
+
67115
+ "data-picker-facade": ui === undefined ? undefined : ""
66717
67116
  // A button's label is not a placeholder, however empty the
66718
67117
  // picker behind it is.
66719
67118
  ,
@@ -78903,8 +79302,8 @@ const StepList = ({
78903
79302
  }
78904
79303
  const rootElement = rootRef.current;
78905
79304
  const read = () => {
78906
- const currentArea = containerElement.getAttribute("data-slide-current");
78907
- const towardArea = containerElement.getAttribute("data-slide-travel-toward");
79305
+ const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
79306
+ const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
78908
79307
  setContainerCurrent(currentArea ?? undefined);
78909
79308
  const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
78910
79309
  if (currentIdx === -1) {
@@ -78928,13 +79327,12 @@ const StepList = ({
78928
79327
  rootElement.style.setProperty("--step-list-pos-dx", dx);
78929
79328
  };
78930
79329
  read();
78931
- const observer = new MutationObserver(read);
78932
- observer.observe(containerElement, {
78933
- attributes: true,
78934
- attributeFilter: ["data-slide-current", "data-slide-travel-toward"]
78935
- });
79330
+ // Read off the container, and re-read when it says something has changed:
79331
+ // a write is not a change, and the attribute the halo follows is written on
79332
+ // every frame of a gesture with the same value in it.
79333
+ containerElement.addEventListener(SLIDE_STATE_EVENT, read);
78936
79334
  return () => {
78937
- observer.disconnect();
79335
+ containerElement.removeEventListener(SLIDE_STATE_EVENT, read);
78938
79336
  };
78939
79337
  // width: the dots move when the room does, and the written positions are
78940
79338
  // pixels of those dots.
@@ -79765,5 +80163,5 @@ const UserSvg = () => jsx("svg", {
79765
80163
  })
79766
80164
  });
79767
80165
 
79768
- export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, 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, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, 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 };
80166
+ export { ActionRenderer, ActiveKeyboardShortcuts, Address, Badge, BadgeCount, BadgeList, Binder, Box, Button, ButtonCopyToClipboard, CalloutStatusIcon, Caption, CardLayout, CheckSvg, CheckboxGroup, CloseSvg, Code, Col, Colgroup, Color, ConstructionSvg, ControlGroup, DaySpin, Details, Dialog, Editable, ErrorBoundary, ErrorBoundaryContext, ExclamationSvg, Expandable, EyeClosedSvg, EyeSvg, Field, FixedBar, Form, Group, Head, HeartSvg, HomeSvg, Icon, Image, InfoSvg, Input, InputDuration, Interpolate, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, LinkCurrentSvg, List, ListItem, ListItemGroup, ListItems, Loading, LoadingDotsSvg, LoadingIndicator, LoadingIndicatorFluid, LoadingOutline, MessageBox, Meter, Nav, NaviDebug, NumberSpin, Paragraph, Picker, Popover, Popup, Quantity, RadioGroup, Route, RouteTransitionArea, RouteTravel, RowNumberCol, RowNumberTableCell, SVGMaskOverlay, SearchSvg, Select, SelectableInput, SelectionContext, Separator, SettingsSvg, SidePanel, Slide, SlideContainer, Spin, SpinGroup, SplitButton, StarSvg, Step, StepList, SummaryMarker, Svg, Table, TableCell, Tbody, Text, TextBox, Textarea, TextareaCharCount, Thead, Time, TimeRangeSpin, TimeRangeWheel, TimeSpin, TimeWheel, Title, Tr, UITransition, Unit, UserSvg, ViewportLayout, Wheel, WheelGroup, WheelItem, actionRunEffect, anyMatchingRouteSignal, applySearch, arraySignalMembership, canNavBackSignal, canNavForwardSignal, coarsePointerSignal, compareTwoJsValues, constraintFromValidityRule, createAction, createAvailableConstraint, createI18n, createRequestCanceller, createSearch, createSelectionKeyboardShortcuts, createSlot, defineInteractionDetector, 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, useCalloutElement, useCalloutRequestClose, useCanNavBack, useCanNavForward, useCancelPrevious, useCellGridFromRows, useConstraintValidityState, useDependenciesDiff, useDisplayedLayoutEffect, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useInputGroup, useKeyboardShortcuts, useNavState, useOrderedColumns, usePopupMode, useRouteStatus, useRunOnMount, useSearchText, useSelectableElement, useSelectionController, useSignalSync, useSlideContainer, useSlideValue, useStateArray, useTitleLevel, useUrlSearchParam, useUrlTargetId, valueInLocalStorage, windowWidthSignal };
79769
80167
  //# sourceMappingURL=jsenv_navi.js.map