@jsenv/navi 0.29.120 → 0.29.121

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,155 @@ const LinkCurrentIndicator = () => {
39602
39650
  };
39603
39651
  markAsOutsideTextFlow(LinkCurrentIndicator);
39604
39652
 
39653
+ /**
39654
+ * What a SlideContainer is doing, read from outside it.
39655
+ *
39656
+ * Only slides go in the box, so everything drawn AROUND the travel is written
39657
+ * around it — a chevron pinned to the edge of a full-screen viewer, a "3 / 8"
39658
+ * counter, a bar. Those need to know things the box alone knows: where one is,
39659
+ * and whether there is anywhere to go that way. A container driven by nobody
39660
+ * (no `current`, no `signal`) knows them and no one else does, so this is also
39661
+ * what keeps such a container usable without lifting its state out of it.
39662
+ *
39663
+ * Two channels, and they answer two different questions. The attributes the box
39664
+ * paints on itself are the STATE: they are there for whoever arrives later, and
39665
+ * CSS draws with them without any of this ([data-slide-ways~="right"]). The
39666
+ * event it dispatches is the NEWS: it says WHEN something changed, which no
39667
+ * attribute can say. So this reads the first and listens to the second.
39668
+ *
39669
+ * Not watching the DOM for it, which is the tempting third way and the wrong
39670
+ * one: a write is not a change. Under a finger the box writes where the picture
39671
+ * leans on every frame, with the same value in it more often than not, and a
39672
+ * watcher is woken for every one of them — while the box itself knows perfectly
39673
+ * well whether anything is different, and says so once.
39674
+ *
39675
+ * Nothing is told to the box in return, and nothing has to be wired: it
39676
+ * announces to itself and to its followers, so the thing doing the reading sits
39677
+ * anywhere on the page — in a fixed bar, in the frame around the box, in a
39678
+ * dialog holding it — and a container which never re-renders (a travel it drove
39679
+ * itself) is still followed.
39680
+ *
39681
+ * Reading only. Asking for a travel is what it always was: a command aimed at
39682
+ * the box (`commandFor={id}` + `--navi-right`, or triggerNaviCommand) — one way
39683
+ * of asking, wherever the asking is written.
39684
+ */
39685
+
39686
+
39687
+ // The vocabulary the box publishes, in one place: written by slide_container,
39688
+ // read here and by anything styling in CSS alone
39689
+ // ([data-slide-ways~="right"]).
39690
+ const SLIDE_CURRENT_ATTRIBUTE = "data-slide-current";
39691
+ 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.
39694
+ 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.
39700
+ const SLIDE_HELD_ATTRIBUTE = "data-slide-held";
39701
+ // …and the one word said out loud, on the box and on every follower of it, when
39702
+ // any of the above has actually changed. It carries the whole state as its
39703
+ // detail, so `onnavi_slide_state` on the box is a way of hearing it too.
39704
+ const SLIDE_STATE_EVENT = "navi_slide_state";
39705
+
39706
+ const NO_WAYS = [];
39707
+ const NOTHING = {
39708
+ current: undefined,
39709
+ toward: undefined,
39710
+ areas: NO_WAYS,
39711
+ ways: NO_WAYS,
39712
+ held: NO_WAYS,
39713
+ };
39714
+
39715
+ const wordsOf = (element, attribute) => {
39716
+ const value = element.getAttribute(attribute);
39717
+ return value ? value.split(" ") : NO_WAYS;
39718
+ };
39719
+
39720
+ const readSlideContainerState = (element) => ({
39721
+ current: element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ?? undefined,
39722
+ 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).
39726
+ areas: Array.from(
39727
+ element.querySelectorAll(":scope > [data-slide-track] > [data-slide]"),
39728
+ (slideElement) =>
39729
+ slideElement.getAttribute("data-slide-area") || slideElement.id || "",
39730
+ ),
39731
+ ways: wordsOf(element, SLIDE_WAYS_ATTRIBUTE),
39732
+ held: wordsOf(element, SLIDE_HELD_ATTRIBUTE),
39733
+ });
39734
+
39735
+ const sameSlideContainerState = (a, b) =>
39736
+ a.current === b.current &&
39737
+ a.toward === b.toward &&
39738
+ a.areas.join(" ") === b.areas.join(" ") &&
39739
+ a.ways.join(" ") === b.ways.join(" ") &&
39740
+ a.held.join(" ") === b.held.join(" ");
39741
+
39742
+ /**
39743
+ * @param {string|Element|{current: Element}} [target] - the container: its id
39744
+ * (the way everything else addresses one), the element, or a ref to it. Not a
39745
+ * 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.
39749
+ * @returns {{
39750
+ * current: string|undefined,
39751
+ * toward: string|undefined,
39752
+ * areas: string[],
39753
+ * can: (direction: "left"|"right"|"up"|"down") => boolean,
39754
+ * held: (direction: "left"|"right"|"up"|"down") => boolean,
39755
+ * }} where the box stands. `current` is the slide on screen — the one being
39756
+ * travelled TO while a travel plays, because that is what one is looking at;
39757
+ * `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.
39761
+ */
39762
+ const useSlideContainer = (target) => {
39763
+ const [state, setState] = useState(NOTHING);
39764
+
39765
+ useLayoutEffect(() => {
39766
+ const element =
39767
+ typeof target === "string"
39768
+ ? document.getElementById(target)
39769
+ : target && "current" in target
39770
+ ? target.current
39771
+ : target;
39772
+ if (!element) {
39773
+ setState(NOTHING);
39774
+ return undefined;
39775
+ }
39776
+ // Read off the DOM rather than taken from the event's detail, even though
39777
+ // the two say the same thing: the first read has no event to take it from
39778
+ // (the box was already standing somewhere when this mounted), and one way
39779
+ // of reading is one thing that can be wrong.
39780
+ const read = () => {
39781
+ const nextState = readSlideContainerState(element);
39782
+ setState((previous) =>
39783
+ sameSlideContainerState(previous, nextState) ? previous : nextState,
39784
+ );
39785
+ };
39786
+ read();
39787
+ element.addEventListener(SLIDE_STATE_EVENT, read);
39788
+ return () => {
39789
+ element.removeEventListener(SLIDE_STATE_EVENT, read);
39790
+ };
39791
+ }, [target]);
39792
+
39793
+ return {
39794
+ current: state.current,
39795
+ toward: state.toward,
39796
+ areas: state.areas,
39797
+ can: (direction) => state.ways.includes(direction),
39798
+ held: (direction) => state.held.includes(direction),
39799
+ };
39800
+ };
39801
+
39605
39802
  installImportMetaCssBuild(import.meta);/**
39606
39803
  * TabList component with support for horizontal and vertical layouts
39607
39804
  * https://dribbble.com/search/tabs
@@ -40000,19 +40197,17 @@ const Nav = ({
40000
40197
  }
40001
40198
  slideContainerElementRef.current = containerElement;
40002
40199
  const readContainer = () => {
40003
- setCurrentSlideArea(containerElement.getAttribute("data-slide-current") ?? undefined);
40200
+ setCurrentSlideArea(containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ?? undefined);
40004
40201
  paintIndicatorGeometryRef.current();
40005
40202
  };
40006
40203
  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
- });
40204
+ // Where one is and what the picture leans on are written on the container,
40205
+ // and the container says out loud when either has changed: this row is not
40206
+ // in the box (it sits above it, in a fixed bar, anywhere), so it reads the
40207
+ // first and listens to the second. Listening rather than watching the DOM
40208
+ // because a write is not a change — the attribute the trait follows is
40209
+ // written on every frame of a gesture with the same value in it.
40210
+ containerElement.addEventListener(SLIDE_STATE_EVENT, readContainer);
40016
40211
  // A row whose tabs changed width — a badge count, a font that just
40017
40212
  // arrived, a window resized — is measured again: what was written is
40018
40213
  // pixels, and pixels go stale.
@@ -40021,7 +40216,7 @@ const Nav = ({
40021
40216
  });
40022
40217
  sizeObserver.observe(navRef.current);
40023
40218
  return () => {
40024
- attributeObserver.disconnect();
40219
+ containerElement.removeEventListener(SLIDE_STATE_EVENT, readContainer);
40025
40220
  sizeObserver.disconnect();
40026
40221
  slideContainerElementRef.current = null;
40027
40222
  };
@@ -49916,6 +50111,13 @@ const readArea = slideElement => slideElement.getAttribute("data-slide-area") ||
49916
50111
  * screens are steps rather than places — so the keys keep the meaning the
49917
50112
  * content gives them, and travelling stays something one asks for (a button,
49918
50113
  * a command).
50114
+ * WHERE they are heard is a separate question, and it is not answered here: a
50115
+ * key only ever reaches what has the focus, so this box hears the ones pressed
50116
+ * inside it — and every follower of it hears the rest. A chevron pinned to the
50117
+ * edge of a full-screen surface is outside the box by necessity (only slides
50118
+ * go in it), so the surface says `data-slide-container-follows={id}` and the
50119
+ * arrows keep walking wherever the keyboard is in it. See the paragraph above
50120
+ * about what is drawn AROUND the travel.
49919
50121
  * @param {boolean|"x"|"y"|"xy"} [props.travelByDrag=true] - whether a pointer
49920
50122
  * dragging the slides travels. On by default: slides side by side are
49921
50123
  * something one expects to push around with a thumb. Off where the gesture
@@ -50236,7 +50438,7 @@ const SlideContainer = ({
50236
50438
  // there is nothing else for it to read — a slide the container holds by
50237
50439
  // itself is known to no one else.
50238
50440
  const paintCurrentArea = area => {
50239
- containerRef.current?.setAttribute("data-slide-current", area);
50441
+ containerRef.current?.setAttribute(SLIDE_CURRENT_ATTRIBUTE, area);
50240
50442
  };
50241
50443
  const markAnswered = area => {
50242
50444
  const order = readMap().slideElements.map(readArea);
@@ -50559,6 +50761,14 @@ const SlideContainer = ({
50559
50761
  if (!stageRef.current) {
50560
50762
  drawnAreaRef.current = currentArea;
50561
50763
  }
50764
+ // Said last, and after data-current has been written: what a travel would do
50765
+ // is read off the map from the slide that is now current, and off the locks
50766
+ // that slide is wearing in this very commit.
50767
+ paintWays(currentElement);
50768
+ // …and once everything this commit had to write is written, the box says so
50769
+ // — a single announcement for a render that may have changed several of the
50770
+ // facts at once.
50771
+ announceState();
50562
50772
  // The finger has the last word: everything above drew the map at rest, and
50563
50773
  // where the track actually is right now is where the gesture put it.
50564
50774
  paintDrag();
@@ -51086,11 +51296,94 @@ const SlideContainer = ({
51086
51296
  const paintTravelToward = area => {
51087
51297
  for (const element of travelPainters()) {
51088
51298
  if (area) {
51089
- element.setAttribute("data-slide-travel-toward", area);
51299
+ element.setAttribute(SLIDE_TOWARD_ATTRIBUTE, area);
51090
51300
  } else {
51091
- element.removeAttribute("data-slide-travel-toward");
51301
+ element.removeAttribute(SLIDE_TOWARD_ATTRIBUTE);
51092
51302
  }
51093
51303
  }
51304
+ // The one fact that changes without a render behind it: under a finger this
51305
+ // is written per frame, so the announcement is left to say whether any of
51306
+ // those writes was a change.
51307
+ announceState();
51308
+ };
51309
+
51310
+ // What a travel would do right now, said in two lists because they are two
51311
+ // different facts: `ways` is where one WOULD go — there is a slide that way
51312
+ // and the one on screen lets go of it — and `held` is where there is a slide
51313
+ // that way and this one holds on to the user (preventNav, a `required` step
51314
+ // still unanswered). A way out leading nowhere is not there; a way out being
51315
+ // held is there and says no, which is why it stays visible and explainable.
51316
+ //
51317
+ // Painted rather than rendered, and on the followers too: only slides go in
51318
+ // this box, so the chevrons, the counters and the bars that go with it are
51319
+ // written AROUND it — in CSS off these attributes
51320
+ // ([data-slide-ways~="right"]), or through useSlideContainer, which watches
51321
+ // them. Nothing is told, everything is read: it is what lets them sit
51322
+ // anywhere, and what lets a container nobody drives still be followed.
51323
+ const paintWays = currentElement => {
51324
+ const ways = [];
51325
+ const held = [];
51326
+ for (const direction of Object.keys(DIRECTIONS)) {
51327
+ const {
51328
+ dx,
51329
+ dy
51330
+ } = DIRECTIONS[direction];
51331
+ if (!mapAxes?.includes(dx ? "x" : "y")) {
51332
+ // Not an axis this map has: a row has no up and no down, and saying so
51333
+ // would be saying it about every row on the page.
51334
+ continue;
51335
+ }
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);
51342
+ }
51343
+ for (const element of travelPainters()) {
51344
+ if (ways.length) {
51345
+ element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
51346
+ } else {
51347
+ element.removeAttribute(SLIDE_WAYS_ATTRIBUTE);
51348
+ }
51349
+ if (held.length) {
51350
+ element.setAttribute(SLIDE_HELD_ATTRIBUTE, held.join(" "));
51351
+ } else {
51352
+ element.removeAttribute(SLIDE_HELD_ATTRIBUTE);
51353
+ }
51354
+ }
51355
+ };
51356
+
51357
+ // The news, as opposed to the state. Everything painted above IS the state:
51358
+ // it is what anything arriving later reads, and what CSS draws with. What an
51359
+ // attribute cannot say is WHEN it changed — and watching it is the wrong way
51360
+ // to ask, because a write is not a change: `data-slide-travel-toward` is
51361
+ // written on every frame of a gesture with the same value in it, and every
51362
+ // watcher would be woken sixty times a second for nothing. So the box says it
51363
+ // itself, once, and only when something is actually different.
51364
+ //
51365
+ // Said to its followers as well as to itself, exactly as the painting is: a
51366
+ // frame drawn around the box hears what the box is doing without knowing its
51367
+ // id, and a row of tabs beside it listens on the box by id. Not bubbling, like
51368
+ // every other navi event — what is announced is about THIS box, and a box
51369
+ // inside another must not be heard as the one around it.
51370
+ const announcedRef = useRef(null);
51371
+ const announceState = () => {
51372
+ const containerEl = containerRef.current;
51373
+ if (!containerEl) {
51374
+ return;
51375
+ }
51376
+ const state = readSlideContainerState(containerEl);
51377
+ const announced = announcedRef.current;
51378
+ if (announced && sameSlideContainerState(announced, state)) {
51379
+ return;
51380
+ }
51381
+ announcedRef.current = state;
51382
+ for (const element of travelPainters()) {
51383
+ dispatchCustomEvent(element, SLIDE_STATE_EVENT, {
51384
+ ...state
51385
+ });
51386
+ }
51094
51387
  };
51095
51388
 
51096
51389
  // Where the picture stands relative to the slide that is CURRENT, in boxes:
@@ -52002,7 +52295,14 @@ const SlideMove = ({
52002
52295
  label
52003
52296
  } = DIRECTIONS[direction];
52004
52297
  const forward = dx > 0 || dy > 0;
52005
- const locked = forward ? locks?.preventNavNext : locks?.preventNavPrevious;
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.
52304
+ const slides = useSlideContainer(rest.commandFor);
52305
+ const locked = (forward ? locks?.preventNavNext : locks?.preventNavPrevious) || slides.held(direction);
52006
52306
  return jsx(SlideNavButton, {
52007
52307
  command: command,
52008
52308
  locked: locked,
@@ -54555,6 +54855,7 @@ const css$E = /* css */`
54555
54855
  }
54556
54856
  }
54557
54857
 
54858
+ ${surfaceTextCss}
54558
54859
  ${popupCss}
54559
54860
  `;
54560
54861
 
@@ -56052,6 +56353,7 @@ const css$D = /* css */`
56052
56353
  }
56053
56354
  }
56054
56355
 
56356
+ ${surfaceTextCss}
56055
56357
  ${popupCss}
56056
56358
  `;
56057
56359
 
@@ -66172,6 +66474,20 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`
66172
66474
  pointer-events: none;
66173
66475
  user-select: none;
66174
66476
 
66477
+ /* The value the picker draws itself is the control's own text, at the
66478
+ control's font size: it keeps the control line, snapped to the pixel
66479
+ like the field around it. A caller's "ui" is the caller's own drawing
66480
+ of the control, and it is written on the page's line, as the number,
66481
+ so each text it holds keeps a line relative to its own size. The
66482
+ control line is a length (--navi-control-line-height), and inherited
66483
+ it would arrive as the control's pixels: a 14px label under an 18px
66484
+ picker's 23px line carries ~5px of leading above and below that no
66485
+ glyph occupies and nothing on screen explains. Same reason a popup
66486
+ takes the number — see .navi_popup in popup.jsx. */
66487
+ &[data-picker-facade] {
66488
+ line-height: var(--navi-line-height);
66489
+ }
66490
+
66175
66491
  &[navi-placeholder] {
66176
66492
  color: var(--picker-placeholder-color);
66177
66493
  font-style: var(--picker-placeholder-font-style);
@@ -66311,6 +66627,16 @@ installImportMetaCssBuild(import.meta);const css$t = /* css */`
66311
66627
  --x-corner-bottom-left-radius: initial;
66312
66628
 
66313
66629
  display: contents;
66630
+ /* What opens from a control is a page of its own, not part of that
66631
+ control's type scale: it is written at the control font by name,
66632
+ rather than by inheriting the size the picker itself is drawn at. A
66633
+ caller who sizes a picker to the façade it holds (so the padding, the
66634
+ corners and the chevron the picker draws in em land on the text the
66635
+ caller actually wrote) would otherwise take the popup down with it:
66636
+ its title, and everything in it that does not state a size of its
66637
+ own. Same reason the popup is written on the page's line rather than
66638
+ the control's — see .navi_popup in popup.jsx. */
66639
+ font-size: var(--navi-control-font-size);
66314
66640
  text-align: initial; /* Don't inherit picker text align */
66315
66641
  }
66316
66642
 
@@ -66714,6 +67040,12 @@ const PickerButton = props => {
66714
67040
  }
66715
67041
  }), variant === "headless" || ui === "default" ? null : jsx(Text, {
66716
67042
  className: "navi_picker_value"
67043
+ // Tells the caller's own drawing of the control from the value
67044
+ // the picker draws itself, so each is written on its own line
67045
+ // (see .navi_picker_value in the CSS above).
67046
+ ,
67047
+
67048
+ "data-picker-facade": ui === undefined ? undefined : ""
66717
67049
  // A button's label is not a placeholder, however empty the
66718
67050
  // picker behind it is.
66719
67051
  ,
@@ -78903,8 +79235,8 @@ const StepList = ({
78903
79235
  }
78904
79236
  const rootElement = rootRef.current;
78905
79237
  const read = () => {
78906
- const currentArea = containerElement.getAttribute("data-slide-current");
78907
- const towardArea = containerElement.getAttribute("data-slide-travel-toward");
79238
+ const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
79239
+ const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
78908
79240
  setContainerCurrent(currentArea ?? undefined);
78909
79241
  const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
78910
79242
  if (currentIdx === -1) {
@@ -78928,13 +79260,12 @@ const StepList = ({
78928
79260
  rootElement.style.setProperty("--step-list-pos-dx", dx);
78929
79261
  };
78930
79262
  read();
78931
- const observer = new MutationObserver(read);
78932
- observer.observe(containerElement, {
78933
- attributes: true,
78934
- attributeFilter: ["data-slide-current", "data-slide-travel-toward"]
78935
- });
79263
+ // Read off the container, and re-read when it says something has changed:
79264
+ // a write is not a change, and the attribute the halo follows is written on
79265
+ // every frame of a gesture with the same value in it.
79266
+ containerElement.addEventListener(SLIDE_STATE_EVENT, read);
78936
79267
  return () => {
78937
- observer.disconnect();
79268
+ containerElement.removeEventListener(SLIDE_STATE_EVENT, read);
78938
79269
  };
78939
79270
  // width: the dots move when the room does, and the written positions are
78940
79271
  // pixels of those dots.
@@ -79765,5 +80096,5 @@ const UserSvg = () => jsx("svg", {
79765
80096
  })
79766
80097
  });
79767
80098
 
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 };
80099
+ 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
80100
  //# sourceMappingURL=jsenv_navi.js.map