@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.
@@ -49208,10 +49208,10 @@ const css$R = /* css */`.navi_expandable {
49208
49208
  }
49209
49209
 
49210
49210
  & > .navi_expandable_content_container {
49211
- clip-path: inset(-9999px -9999px 0);
49212
49211
  grid-template-rows: 0fr;
49213
49212
  display: grid;
49214
49213
  position: relative;
49214
+ overflow-y: clip;
49215
49215
 
49216
49216
  & > .navi_expandable_content_sizer {
49217
49217
  min-height: 0;
@@ -49224,7 +49224,6 @@ const css$R = /* css */`.navi_expandable {
49224
49224
  }
49225
49225
 
49226
49226
  &[data-open-direction="up"] > .navi_expandable_content_container {
49227
- clip-path: inset(0 -9999px -9999px);
49228
49227
  align-content: end;
49229
49228
 
49230
49229
  & > .navi_expandable_content_sizer {
@@ -49246,9 +49245,9 @@ const css$R = /* css */`.navi_expandable {
49246
49245
  }
49247
49246
 
49248
49247
  & > .navi_expandable_content_container {
49249
- clip-path: inset(-9999px 0 0 -9999px);
49250
49248
  grid-template-rows: 0fr;
49251
49249
  grid-template-columns: 0fr;
49250
+ overflow: clip;
49252
49251
 
49253
49252
  & > .navi_expandable_content_sizer {
49254
49253
  min-width: 0;
@@ -49266,7 +49265,6 @@ const css$R = /* css */`.navi_expandable {
49266
49265
  }
49267
49266
 
49268
49267
  &[data-open-direction="left"] > .navi_expandable_content_container {
49269
- clip-path: inset(-9999px -9999px 0 0);
49270
49268
  justify-content: end;
49271
49269
 
49272
49270
  & > .navi_expandable_content_sizer {
@@ -49288,11 +49286,10 @@ const css$R = /* css */`.navi_expandable {
49288
49286
  }
49289
49287
 
49290
49288
  &[aria-expanded="true"][data-settled] > .navi_expandable_content_container {
49291
- clip-path: none;
49289
+ overflow: visible;
49292
49290
  }
49293
49291
 
49294
49292
  &:not([aria-expanded="true"])[data-settled] > .navi_expandable_content_container {
49295
- clip-path: none;
49296
49293
  overflow: clip;
49297
49294
  }
49298
49295
 
@@ -49389,11 +49386,12 @@ const useExpandableContext = partName => {
49389
49386
  * that can scroll so the content revealed past its edge is on screen: an
49390
49387
  * expandable opening at the bottom of a list otherwise grows below the
49391
49388
  * fold, and the user sees the row grow but not what it grew with. Measured
49392
- * on the open layout and done once per opening, alongside the reveal
49393
- * (smoothly when `animation` plays, at once otherwise). An expandable
49394
- * taller than the scroller keeps its UI part on screen and the content
49395
- * follows as far as it fits. Only the nearest scroller moves — a popup or
49396
- * a page around it stays where it is.
49389
+ * on the open layout, once per opening; when `animation` plays the
49390
+ * scroller rides along with the reveal, at the track's own progress, and
49391
+ * otherwise it goes there at once. A `scroll-margin` on the expandable is
49392
+ * honored. An expandable taller than the scroller keeps its UI part on
49393
+ * screen and the content follows as far as it fits. Only the nearest
49394
+ * scroller moves — a popup or a page around it stays where it is.
49397
49395
  * @param maxContentHeight - Caps the content height; taller content scrolls
49398
49396
  * inside the expandable instead of growing it.
49399
49397
  * @param mount - When the content is built and thrown away, a popup's own
@@ -49516,15 +49514,27 @@ const Expandable = props => {
49516
49514
  cancelSettleWatchRef.current?.();
49517
49515
  cancelSettleWatchRef.current = null;
49518
49516
  };
49519
- const watchSettle = () => {
49517
+ // `stopRide`, when the scroller rides along with the reveal (keepInView):
49518
+ // the ride is part of the movement, so it ends with it — settled or
49519
+ // cancelled — and never on its own clock.
49520
+ const watchSettle = stopRide => {
49520
49521
  const contentContainer = contentContainerRef.current;
49521
49522
  const contentElement = contentContainer.firstElementChild.firstElementChild;
49522
- cancelSettleWatchRef.current = whenTransitionSettles(contentContainer, () => {
49523
+ const cancelWatch = whenTransitionSettles(contentContainer, () => {
49523
49524
  cancelSettleWatchRef.current = null;
49525
+ if (stopRide) {
49526
+ stopRide();
49527
+ }
49524
49528
  contentElement.style.width = "";
49525
49529
  contentElement.style.height = "";
49526
49530
  setSettled(true);
49527
49531
  });
49532
+ cancelSettleWatchRef.current = () => {
49533
+ if (stopRide) {
49534
+ stopRide();
49535
+ }
49536
+ cancelWatch();
49537
+ };
49528
49538
  };
49529
49539
 
49530
49540
  // The reveal needs the content at its final size before the track starts
@@ -49543,17 +49553,22 @@ const Expandable = props => {
49543
49553
  } else {
49544
49554
  contentElement.style.height = `${finalRect.height}px`;
49545
49555
  }
49556
+ let stopRide = null;
49546
49557
  if (keepInView) {
49547
- // Here and not once the reveal settles: the layout is the open one at
49548
- // this point, so where the content ends is known before the movement
49549
- // starts, and the scroll can travel along with it. The frozen content
49550
- // overflowing the collapsed track is still scrollable overflow (the
49551
- // one-sided clip-path, see the CSS), so the scroller can reach that
49552
- // position from the first frame.
49553
- scrollExpandableIntoView(rootRef.current, {
49554
- revealDirection,
49555
- behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "instant" : "smooth"
49556
- });
49558
+ // Measured here and not once the reveal settles: the layout is the open
49559
+ // one at this point, so where the content ends is known before the
49560
+ // movement starts. The scroll then rides the movement rather than
49561
+ // running ahead of it (see rideScrollerAlongReveal).
49562
+ const revealScroll = measureRevealScroll(rootRef.current, revealDirection);
49563
+ if (revealScroll) {
49564
+ const finalContainerRect = contentContainer.getBoundingClientRect();
49565
+ stopRide = rideScrollerAlongReveal(revealScroll, {
49566
+ contentContainer,
49567
+ axis: isColumn ? "x" : "y",
49568
+ startSize: isColumn ? startRect.width : startRect.height,
49569
+ finalSize: isColumn ? finalContainerRect.width : finalContainerRect.height
49570
+ });
49571
+ }
49557
49572
  }
49558
49573
  // Put the tracks back where the last paint left them and let the
49559
49574
  // transition play from there. In fr — px does not interpolate with fr.
@@ -49576,6 +49591,7 @@ const Expandable = props => {
49576
49591
  contentContainer.style.transitionProperty = "";
49577
49592
  contentContainer.style.gridTemplateColumns = "";
49578
49593
  contentContainer.style.gridTemplateRows = "";
49594
+ return stopRide;
49579
49595
  };
49580
49596
 
49581
49597
  // What opening LOOKS like here, and how to undo it — the one thing an
@@ -49607,12 +49623,17 @@ const Expandable = props => {
49607
49623
  // transfer there is nothing to restore on close either.
49608
49624
  const restoreFocus = autoFocus && !silent && contentContainer ? openController.transferFocusOnOpen(contentContainer) : null;
49609
49625
  if (revealing) {
49610
- armReveal(startRect);
49611
- watchSettle();
49626
+ const stopRide = armReveal(startRect);
49627
+ watchSettle(stopRide);
49612
49628
  } else if (keepInView && !silent && contentContainer) {
49613
- scrollExpandableIntoView(rootRef.current, {
49614
- revealDirection
49615
- });
49629
+ const revealScroll = measureRevealScroll(rootRef.current, revealDirection);
49630
+ if (revealScroll) {
49631
+ // No behavior: the scroller's own scroll-behavior decides.
49632
+ revealScroll.scrollerEl.scrollTo({
49633
+ left: revealScroll.left,
49634
+ top: revealScroll.top
49635
+ });
49636
+ }
49616
49637
  }
49617
49638
  if (!silent) {
49618
49639
  rootRef.current.dispatchEvent(createToggleEvent(true));
@@ -49654,7 +49675,7 @@ const Expandable = props => {
49654
49675
  }
49655
49676
  }
49656
49677
  if (collapsing) {
49657
- watchSettle();
49678
+ watchSettle(null);
49658
49679
  }
49659
49680
  rootRef.current.dispatchEvent(createToggleEvent(false));
49660
49681
  };
@@ -49994,16 +50015,14 @@ Expandable.Content = ExpandableContent;
49994
50015
  // the one exception, excluded at the call site.
49995
50016
  const UI_INTERACTIVE_SELECTOR = ["a[href]", "button", "input", "select", "textarea", "label", "[role='button']", "[contenteditable='']", "[contenteditable='true']", "audio[controls]", "video[controls]"].join(", ");
49996
50017
 
49997
- // Scrolls the nearest scroller that can scroll so the whole expandable — UI
49998
- // part and revealed content — is in view. Measured on the layout as it stands,
49999
- // so the caller calls it while the open layout is the one laid out (see
50000
- // openEffect). A root taller than the view is aligned on its UI side instead:
50001
- // what the user pressed stays on screen, and the content follows as far as it
50002
- // fits. One scroller only — a popup or a page around it is left where it is.
50003
- const scrollExpandableIntoView = (rootEl, {
50004
- revealDirection,
50005
- behavior
50006
- }) => {
50018
+ // Where the nearest scroller that can scroll would have to be for the whole
50019
+ // expandable — UI part and revealed content — to be in view. Measured on the
50020
+ // layout as it stands, so the caller measures while the open layout is the
50021
+ // one laid out (see openEffect). A root taller than the view is aligned on
50022
+ // its UI side instead: what the user pressed stays on screen, and the content
50023
+ // follows as far as it fits. One scroller only — a popup or a page around it
50024
+ // is left where it is. Null when there is no scroller, or nothing to scroll.
50025
+ const measureRevealScroll = (rootEl, revealDirection) => {
50007
50026
  let scrollerEl = null;
50008
50027
  for (const scrollContainer of getScrollContainerSet(rootEl)) {
50009
50028
  if (isScrollable(scrollContainer)) {
@@ -50012,7 +50031,7 @@ const scrollExpandableIntoView = (rootEl, {
50012
50031
  }
50013
50032
  }
50014
50033
  if (!scrollerEl) {
50015
- return;
50034
+ return null;
50016
50035
  }
50017
50036
  const rootRect = rootEl.getBoundingClientRect();
50018
50037
  let block = "nearest";
@@ -50024,12 +50043,74 @@ const scrollExpandableIntoView = (rootEl, {
50024
50043
  } else if (rootRect.width > scrollerEl.clientWidth) {
50025
50044
  inline = revealDirection === "right" ? "start" : "end";
50026
50045
  }
50027
- scrollIntoViewScoped(rootEl, {
50046
+ const {
50047
+ left,
50048
+ top
50049
+ } = getScrollIntoViewScopedOffsets(rootEl, {
50028
50050
  container: scrollerEl,
50029
50051
  block,
50030
- inline,
50031
- behavior
50052
+ inline
50032
50053
  });
50054
+ if (left === scrollerEl.scrollLeft && top === scrollerEl.scrollTop) {
50055
+ return null;
50056
+ }
50057
+ return {
50058
+ scrollerEl,
50059
+ left,
50060
+ top
50061
+ };
50062
+ };
50063
+
50064
+ // Moves the scroller toward where the reveal ends at the pace of the reveal
50065
+ // itself: every frame the track's progress is read from the container's size
50066
+ // and the scroll stands the same fraction of its own way. The scroll cannot
50067
+ // run ahead of the track: only the box's own growth extends the scroller's
50068
+ // range, and what the target adds around the box (a parent's padding, a
50069
+ // scroll-margin) becomes reachable as the track grows — a scroll started at
50070
+ // once would be clamped short of it. One clock, the transition's, so the
50071
+ // content never waits below the fold either. Returns what stops the ride.
50072
+ const rideScrollerAlongReveal = ({
50073
+ scrollerEl,
50074
+ left,
50075
+ top
50076
+ }, {
50077
+ contentContainer,
50078
+ axis,
50079
+ startSize,
50080
+ finalSize
50081
+ }) => {
50082
+ const startLeft = scrollerEl.scrollLeft;
50083
+ const startTop = scrollerEl.scrollTop;
50084
+ const leftDelta = left - startLeft;
50085
+ const topDelta = top - startTop;
50086
+ let frameId;
50087
+ const tick = () => {
50088
+ const rect = contentContainer.getBoundingClientRect();
50089
+ const size = axis === "x" ? rect.width : rect.height;
50090
+ let progress = 1;
50091
+ if (finalSize !== startSize) {
50092
+ progress = (size - startSize) / (finalSize - startSize);
50093
+ if (progress < 0) {
50094
+ progress = 0;
50095
+ } else if (progress > 1) {
50096
+ progress = 1;
50097
+ }
50098
+ }
50099
+ scrollerEl.scrollTo({
50100
+ left: startLeft + leftDelta * progress,
50101
+ top: startTop + topDelta * progress,
50102
+ // Driven frame by frame: a smooth scroll-behavior on the scroller is
50103
+ // for a scroll that lands, not for one that is held.
50104
+ behavior: "instant"
50105
+ });
50106
+ if (progress < 1) {
50107
+ frameId = requestAnimationFrame(tick);
50108
+ }
50109
+ };
50110
+ frameId = requestAnimationFrame(tick);
50111
+ return () => {
50112
+ cancelAnimationFrame(frameId);
50113
+ };
50033
50114
  };
50034
50115
  const createToggleEvent = open => {
50035
50116
  const newState = open ? "open" : "closed";