@jsenv/navi 0.29.390 → 0.29.391

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.
@@ -50338,10 +50338,10 @@ const css$R = /* css */`.navi_expandable {
50338
50338
  }
50339
50339
 
50340
50340
  & > .navi_expandable_content_container {
50341
- clip-path: inset(-9999px -9999px 0);
50342
50341
  grid-template-rows: 0fr;
50343
50342
  display: grid;
50344
50343
  position: relative;
50344
+ overflow-y: clip;
50345
50345
 
50346
50346
  & > .navi_expandable_content_sizer {
50347
50347
  min-height: 0;
@@ -50354,7 +50354,6 @@ const css$R = /* css */`.navi_expandable {
50354
50354
  }
50355
50355
 
50356
50356
  &[data-open-direction="up"] > .navi_expandable_content_container {
50357
- clip-path: inset(0 -9999px -9999px);
50358
50357
  align-content: end;
50359
50358
 
50360
50359
  & > .navi_expandable_content_sizer {
@@ -50376,9 +50375,9 @@ const css$R = /* css */`.navi_expandable {
50376
50375
  }
50377
50376
 
50378
50377
  & > .navi_expandable_content_container {
50379
- clip-path: inset(-9999px 0 0 -9999px);
50380
50378
  grid-template-rows: 0fr;
50381
50379
  grid-template-columns: 0fr;
50380
+ overflow: clip;
50382
50381
 
50383
50382
  & > .navi_expandable_content_sizer {
50384
50383
  min-width: 0;
@@ -50396,7 +50395,6 @@ const css$R = /* css */`.navi_expandable {
50396
50395
  }
50397
50396
 
50398
50397
  &[data-open-direction="left"] > .navi_expandable_content_container {
50399
- clip-path: inset(-9999px -9999px 0 0);
50400
50398
  justify-content: end;
50401
50399
 
50402
50400
  & > .navi_expandable_content_sizer {
@@ -50418,11 +50416,10 @@ const css$R = /* css */`.navi_expandable {
50418
50416
  }
50419
50417
 
50420
50418
  &[aria-expanded="true"][data-settled] > .navi_expandable_content_container {
50421
- clip-path: none;
50419
+ overflow: visible;
50422
50420
  }
50423
50421
 
50424
50422
  &:not([aria-expanded="true"])[data-settled] > .navi_expandable_content_container {
50425
- clip-path: none;
50426
50423
  overflow: clip;
50427
50424
  }
50428
50425
 
@@ -50519,11 +50516,12 @@ const useExpandableContext = partName => {
50519
50516
  * that can scroll so the content revealed past its edge is on screen: an
50520
50517
  * expandable opening at the bottom of a list otherwise grows below the
50521
50518
  * fold, and the user sees the row grow but not what it grew with. Measured
50522
- * on the open layout and done once per opening, alongside the reveal
50523
- * (smoothly when `animation` plays, at once otherwise). An expandable
50524
- * taller than the scroller keeps its UI part on screen and the content
50525
- * follows as far as it fits. Only the nearest scroller moves — a popup or
50526
- * a page around it stays where it is.
50519
+ * on the open layout, once per opening; when `animation` plays the
50520
+ * scroller rides along with the reveal, at the track's own progress, and
50521
+ * otherwise it goes there at once. A `scroll-margin` on the expandable is
50522
+ * honored. An expandable taller than the scroller keeps its UI part on
50523
+ * screen and the content follows as far as it fits. Only the nearest
50524
+ * scroller moves — a popup or a page around it stays where it is.
50527
50525
  * @param maxContentHeight - Caps the content height; taller content scrolls
50528
50526
  * inside the expandable instead of growing it.
50529
50527
  * @param mount - When the content is built and thrown away, a popup's own
@@ -50646,15 +50644,27 @@ const Expandable = props => {
50646
50644
  cancelSettleWatchRef.current?.();
50647
50645
  cancelSettleWatchRef.current = null;
50648
50646
  };
50649
- const watchSettle = () => {
50647
+ // `stopRide`, when the scroller rides along with the reveal (keepInView):
50648
+ // the ride is part of the movement, so it ends with it — settled or
50649
+ // cancelled — and never on its own clock.
50650
+ const watchSettle = stopRide => {
50650
50651
  const contentContainer = contentContainerRef.current;
50651
50652
  const contentElement = contentContainer.firstElementChild.firstElementChild;
50652
- cancelSettleWatchRef.current = whenTransitionSettles(contentContainer, () => {
50653
+ const cancelWatch = whenTransitionSettles(contentContainer, () => {
50653
50654
  cancelSettleWatchRef.current = null;
50655
+ if (stopRide) {
50656
+ stopRide();
50657
+ }
50654
50658
  contentElement.style.width = "";
50655
50659
  contentElement.style.height = "";
50656
50660
  setSettled(true);
50657
50661
  });
50662
+ cancelSettleWatchRef.current = () => {
50663
+ if (stopRide) {
50664
+ stopRide();
50665
+ }
50666
+ cancelWatch();
50667
+ };
50658
50668
  };
50659
50669
 
50660
50670
  // The reveal needs the content at its final size before the track starts
@@ -50673,17 +50683,22 @@ const Expandable = props => {
50673
50683
  } else {
50674
50684
  contentElement.style.height = `${finalRect.height}px`;
50675
50685
  }
50686
+ let stopRide = null;
50676
50687
  if (keepInView) {
50677
- // Here and not once the reveal settles: the layout is the open one at
50678
- // this point, so where the content ends is known before the movement
50679
- // starts, and the scroll can travel along with it. The frozen content
50680
- // overflowing the collapsed track is still scrollable overflow (the
50681
- // one-sided clip-path, see the CSS), so the scroller can reach that
50682
- // position from the first frame.
50683
- scrollExpandableIntoView(rootRef.current, {
50684
- revealDirection,
50685
- behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth"
50686
- });
50688
+ // Measured here and not once the reveal settles: the layout is the open
50689
+ // one at this point, so where the content ends is known before the
50690
+ // movement starts. The scroll then rides the movement rather than
50691
+ // running ahead of it (see rideScrollerAlongReveal).
50692
+ const revealScroll = measureRevealScroll(rootRef.current, revealDirection);
50693
+ if (revealScroll) {
50694
+ const finalContainerRect = contentContainer.getBoundingClientRect();
50695
+ stopRide = rideScrollerAlongReveal(revealScroll, {
50696
+ contentContainer,
50697
+ axis: isColumn ? "x" : "y",
50698
+ startSize: isColumn ? startRect.width : startRect.height,
50699
+ finalSize: isColumn ? finalContainerRect.width : finalContainerRect.height
50700
+ });
50701
+ }
50687
50702
  }
50688
50703
  // Put the tracks back where the last paint left them and let the
50689
50704
  // transition play from there. In fr — px does not interpolate with fr.
@@ -50706,6 +50721,7 @@ const Expandable = props => {
50706
50721
  contentContainer.style.transitionProperty = "";
50707
50722
  contentContainer.style.gridTemplateColumns = "";
50708
50723
  contentContainer.style.gridTemplateRows = "";
50724
+ return stopRide;
50709
50725
  };
50710
50726
 
50711
50727
  // What opening LOOKS like here, and how to undo it — the one thing an
@@ -50737,12 +50753,17 @@ const Expandable = props => {
50737
50753
  // transfer there is nothing to restore on close either.
50738
50754
  const restoreFocus = autoFocus && !silent && contentContainer ? openController.transferFocusOnOpen(contentContainer) : null;
50739
50755
  if (revealing) {
50740
- armReveal(startRect);
50741
- watchSettle();
50756
+ const stopRide = armReveal(startRect);
50757
+ watchSettle(stopRide);
50742
50758
  } else if (keepInView && !silent && contentContainer) {
50743
- scrollExpandableIntoView(rootRef.current, {
50744
- revealDirection
50745
- });
50759
+ const revealScroll = measureRevealScroll(rootRef.current, revealDirection);
50760
+ if (revealScroll) {
50761
+ // No behavior: the scroller's own scroll-behavior decides.
50762
+ revealScroll.scrollerEl.scrollTo({
50763
+ left: revealScroll.left,
50764
+ top: revealScroll.top
50765
+ });
50766
+ }
50746
50767
  }
50747
50768
  if (!silent) {
50748
50769
  rootRef.current.dispatchEvent(createToggleEvent(true));
@@ -50784,7 +50805,7 @@ const Expandable = props => {
50784
50805
  }
50785
50806
  }
50786
50807
  if (collapsing) {
50787
- watchSettle();
50808
+ watchSettle(null);
50788
50809
  }
50789
50810
  rootRef.current.dispatchEvent(createToggleEvent(false));
50790
50811
  };
@@ -51124,16 +51145,14 @@ Expandable.Content = ExpandableContent;
51124
51145
  // the one exception, excluded at the call site.
51125
51146
  const UI_INTERACTIVE_SELECTOR = ["a[href]", "button", "input", "select", "textarea", "label", "[role='button']", "[contenteditable='']", "[contenteditable='true']", "audio[controls]", "video[controls]"].join(", ");
51126
51147
 
51127
- // Scrolls the nearest scroller that can scroll so the whole expandable — UI
51128
- // part and revealed content — is in view. Measured on the layout as it stands,
51129
- // so the caller calls it while the open layout is the one laid out (see
51130
- // openEffect). A root taller than the view is aligned on its UI side instead:
51131
- // what the user pressed stays on screen, and the content follows as far as it
51132
- // fits. One scroller only — a popup or a page around it is left where it is.
51133
- const scrollExpandableIntoView = (rootEl, {
51134
- revealDirection,
51135
- behavior
51136
- }) => {
51148
+ // Where the nearest scroller that can scroll would have to be for the whole
51149
+ // expandable — UI part and revealed content — to be in view. Measured on the
51150
+ // layout as it stands, so the caller measures while the open layout is the
51151
+ // one laid out (see openEffect). A root taller than the view is aligned on
51152
+ // its UI side instead: what the user pressed stays on screen, and the content
51153
+ // follows as far as it fits. One scroller only — a popup or a page around it
51154
+ // is left where it is. Null when there is no scroller, or nothing to scroll.
51155
+ const measureRevealScroll = (rootEl, revealDirection) => {
51137
51156
  let scrollerEl = null;
51138
51157
  for (const scrollContainer of getScrollContainerSet(rootEl)) {
51139
51158
  if (isScrollable(scrollContainer)) {
@@ -51142,7 +51161,7 @@ const scrollExpandableIntoView = (rootEl, {
51142
51161
  }
51143
51162
  }
51144
51163
  if (!scrollerEl) {
51145
- return;
51164
+ return null;
51146
51165
  }
51147
51166
  const rootRect = rootEl.getBoundingClientRect();
51148
51167
  let block = "nearest";
@@ -51154,12 +51173,74 @@ const scrollExpandableIntoView = (rootEl, {
51154
51173
  } else if (rootRect.width > scrollerEl.clientWidth) {
51155
51174
  inline = revealDirection === "right" ? "start" : "end";
51156
51175
  }
51157
- scrollIntoViewScoped(rootEl, {
51176
+ const {
51177
+ left,
51178
+ top
51179
+ } = getScrollIntoViewScopedOffsets(rootEl, {
51158
51180
  container: scrollerEl,
51159
51181
  block,
51160
- inline,
51161
- behavior
51182
+ inline
51162
51183
  });
51184
+ if (left === scrollerEl.scrollLeft && top === scrollerEl.scrollTop) {
51185
+ return null;
51186
+ }
51187
+ return {
51188
+ scrollerEl,
51189
+ left,
51190
+ top
51191
+ };
51192
+ };
51193
+
51194
+ // Moves the scroller toward where the reveal ends at the pace of the reveal
51195
+ // itself: every frame the track's progress is read from the container's size
51196
+ // and the scroll stands the same fraction of its own way. The scroll cannot
51197
+ // run ahead of the track: only the box's own growth extends the scroller's
51198
+ // range, and what the target adds around the box (a parent's padding, a
51199
+ // scroll-margin) becomes reachable as the track grows — a scroll started at
51200
+ // once would be clamped short of it. One clock, the transition's, so the
51201
+ // content never waits below the fold either. Returns what stops the ride.
51202
+ const rideScrollerAlongReveal = ({
51203
+ scrollerEl,
51204
+ left,
51205
+ top
51206
+ }, {
51207
+ contentContainer,
51208
+ axis,
51209
+ startSize,
51210
+ finalSize
51211
+ }) => {
51212
+ const startLeft = scrollerEl.scrollLeft;
51213
+ const startTop = scrollerEl.scrollTop;
51214
+ const leftDelta = left - startLeft;
51215
+ const topDelta = top - startTop;
51216
+ let frameId;
51217
+ const tick = () => {
51218
+ const rect = contentContainer.getBoundingClientRect();
51219
+ const size = axis === "x" ? rect.width : rect.height;
51220
+ let progress = 1;
51221
+ if (finalSize !== startSize) {
51222
+ progress = (size - startSize) / (finalSize - startSize);
51223
+ if (progress < 0) {
51224
+ progress = 0;
51225
+ } else if (progress > 1) {
51226
+ progress = 1;
51227
+ }
51228
+ }
51229
+ scrollerEl.scrollTo({
51230
+ left: startLeft + leftDelta * progress,
51231
+ top: startTop + topDelta * progress,
51232
+ // Driven frame by frame: a smooth scroll-behavior on the scroller is
51233
+ // for a scroll that lands, not for one that is held.
51234
+ behavior: "instant"
51235
+ });
51236
+ if (progress < 1) {
51237
+ frameId = requestAnimationFrame(tick);
51238
+ }
51239
+ };
51240
+ frameId = requestAnimationFrame(tick);
51241
+ return () => {
51242
+ cancelAnimationFrame(frameId);
51243
+ };
51163
51244
  };
51164
51245
  const createToggleEvent = open => {
51165
51246
  const newState = open ? "open" : "closed";