@jsenv/navi 0.29.473 → 0.29.475

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.
@@ -18307,6 +18307,20 @@ const openCallout = (
18307
18307
  // deduped away on the grounds that the anchor did not move.
18308
18308
  callout.updatePosition = () =>
18309
18309
  positioner.update(new CustomEvent(ELEMENT_SIZE_CHANGE));
18310
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList) does
18311
+ // so after the callout measured it. The size observer positionCallout puts
18312
+ // on the message sees the change, but answers a frame later (see observeSize
18313
+ // in @jsenv/dom for why); placed again on the spot, the first painted frame
18314
+ // is the right one. Stopped here, since a popup further up holds the callout
18315
+ // out of flow and does not resize with it.
18316
+ const onContentSizeChange = (sizeChangeEvent) => {
18317
+ sizeChangeEvent.stopPropagation();
18318
+ callout.updatePosition();
18319
+ };
18320
+ calloutElement.addEventListener("navi_size_change", onContentSizeChange);
18321
+ addTeardown(() => {
18322
+ calloutElement.removeEventListener("navi_size_change", onContentSizeChange);
18323
+ });
18310
18324
 
18311
18325
  return callout;
18312
18326
  };
@@ -22584,6 +22598,10 @@ const DROP_CONTAINER_ATTRIBUTE = "data-drop-container";
22584
22598
  // every outcome — a list runs one way, and something being put somewhere goes
22585
22599
  // wherever it is put.
22586
22600
  const AXIS_ATTRIBUTE = "data-drag-axis";
22601
+ // How the items of a reordered list are laid out: "column", "row", or "wrap" for
22602
+ // side by side over several lines. Said nothing, it is read from where the
22603
+ // items stand.
22604
+ const FLOW_ATTRIBUTE = "data-reorder-flow";
22587
22605
  const DELAY_ATTRIBUTE = "data-drag-delay";
22588
22606
  const SLOP_ATTRIBUTE = "data-drag-slop";
22589
22607
  const THRESHOLD_ATTRIBUTE$1 = "data-drag-threshold";
@@ -22636,14 +22654,24 @@ defineInteractionDetector({
22636
22654
  const tellsWhenRefused = types.includes(REFUSE);
22637
22655
  // Read at setup rather than at the press: a container can say it, and it is
22638
22656
  // what the gesture is about rather than something it discovers.
22657
+ const itemFlow =
22658
+ element.closest(`[${FLOW_ATTRIBUTE}]`)?.getAttribute(FLOW_ATTRIBUTE) ||
22659
+ undefined;
22639
22660
  const axisHolder = element.closest(`[${AXIS_ATTRIBUTE}]`);
22640
22661
  const axes =
22641
22662
  axisHolder?.getAttribute(AXIS_ATTRIBUTE) ||
22642
- // A list runs one way, and reordering walks it. Anything else goes wherever
22643
- // the hand takes it: a board has places all around, a thing put somewhere has
22644
- // two axes to be put along, a throw goes where it was thrown and a thing
22645
- // that leaves goes out by whichever edge.
22646
- (canReorder && !canLand && !canToss && !canLeave ? "y" : "xy");
22663
+ // A list runs one way, and reordering walks it: down a column, along a
22664
+ // row, and both for a row that wraps — its copy changes line. Anything
22665
+ // else goes wherever the hand takes it: a board has places all around, a
22666
+ // thing put somewhere has two axes to be put along, a throw goes where it
22667
+ // was thrown and a thing that leaves goes out by whichever edge.
22668
+ (canReorder && !canLand && !canToss && !canLeave
22669
+ ? itemFlow === "row"
22670
+ ? "x"
22671
+ : itemFlow === "wrap"
22672
+ ? "xy"
22673
+ : "y"
22674
+ : "xy");
22647
22675
  // Where the places are, read at setup for the same reason as the axes: they
22648
22676
  // are what the gesture is about, and it is what holds the two sides of it
22649
22677
  // that says them.
@@ -22702,6 +22730,7 @@ defineInteractionDetector({
22702
22730
  : undefined,
22703
22731
  containerElement: dropContainer || undefined,
22704
22732
  outsideOf,
22733
+ itemFlow,
22705
22734
  getItemId: (itemElement) => itemElement.id,
22706
22735
  direction: { x: axes.includes("x"), y: axes.includes("y") },
22707
22736
  // Where it may go, said in the DOM. A thing that is put somewhere stays
@@ -46870,8 +46899,8 @@ const useControlgroupProps = (props, {
46870
46899
  const [boundAction] = useActionBoundToOneParam(action, uiGroupStateController.uiStateSignal);
46871
46900
  // Mirror single-input behaviour: a controlled value with nobody listening
46872
46901
  // makes the group read-only so children don't appear interactive when they
46873
- // can't change. A form or a group around it IS someone listening — that is
46874
- // what will send the value and hand a new one back.
46902
+ // can't change. A form or a group collecting it IS someone listening — that
46903
+ // is what will send the value and hand a new one back.
46875
46904
  const listenedTo = useIsControlListenedTo(props, uiGroupStateController);
46876
46905
  useRef(false);
46877
46906
  const implicitReadOnly = uiGroupStateController.hasValueProp && !listenedTo;
@@ -70210,6 +70239,15 @@ const useDialogProps = props => {
70210
70239
  freezeSize(dialogEl);
70211
70240
  }
70212
70241
  rectEffect.observeSize(dialogEl);
70242
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList)
70243
+ // says so, after the placement read it: placed again on the spot, where
70244
+ // observeSize answers a frame late. Stopped here, since a popup further up
70245
+ // holds this one out of flow and does not resize with it.
70246
+ const onContentSizeChange = sizeChangeEvent => {
70247
+ sizeChangeEvent.stopPropagation();
70248
+ rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
70249
+ };
70250
+ dialogEl.addEventListener("navi_size_change", onContentSizeChange);
70213
70251
  // Exposed for the placement-props effect below, which needs to re-place an
70214
70252
  // already-open dialog.
70215
70253
  repositionRef.current = repositionEvent => {
@@ -70223,6 +70261,7 @@ const useDialogProps = props => {
70223
70261
  };
70224
70262
  addCleanup(() => {
70225
70263
  repositionRef.current = null;
70264
+ dialogEl.removeEventListener("navi_size_change", onContentSizeChange);
70226
70265
  rectEffect.disconnect();
70227
70266
  });
70228
70267
  // A descendant anchored to something inside this dialog (a Callout, a
@@ -72015,6 +72054,18 @@ const usePopoverProps = props => {
72015
72054
  // while open (e.g. an expand/collapse toggle inside it) — not just when
72016
72055
  // the anchor itself moves/resizes/re-anchors.
72017
72056
  rectEffect.observeSize(popoverEl);
72057
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList)
72058
+ // says so, after the placement read it: placed again on the spot, where
72059
+ // observeSize answers a frame late. Stopped here, since a popup further up
72060
+ // holds this one out of flow and does not resize with it.
72061
+ const onContentSizeChange = sizeChangeEvent => {
72062
+ sizeChangeEvent.stopPropagation();
72063
+ rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
72064
+ };
72065
+ popoverEl.addEventListener("navi_size_change", onContentSizeChange);
72066
+ addCleanup(() => {
72067
+ popoverEl.removeEventListener("navi_size_change", onContentSizeChange);
72068
+ });
72018
72069
  // Exposed for the placement-props effect above, which needs to re-place an
72019
72070
  // already-open popover.
72020
72071
  repositionRef.current = repositionEvent => {
@@ -77485,6 +77536,7 @@ const useListScrollSync = ({
77485
77536
  useDuplicateHeaderWarning();
77486
77537
  useStuckWindowWarning({
77487
77538
  ref,
77539
+ getListEl,
77488
77540
  scrollerElResolved,
77489
77541
  renderWindowRef,
77490
77542
  windowLeavesItemsOut,
@@ -79009,6 +79061,7 @@ const readItemsPerLine = listEl => {
79009
79061
  // instead of scrolling — and there is no event missing to notice it by.
79010
79062
  const useStuckWindowWarning = ({
79011
79063
  ref,
79064
+ getListEl,
79012
79065
  scrollerElResolved,
79013
79066
  renderWindowRef,
79014
79067
  windowLeavesItemsOut,
@@ -79554,28 +79607,40 @@ const findItemElementAt = (listEl, index) => {
79554
79607
  }
79555
79608
  return null;
79556
79609
  };
79557
- // What the render window is placed and sized on (see evaluateWindow), along
79558
- // the scroll axis and in viewport coordinates: the size of the screen, the part
79559
- // of the list on it (the band), and the items drawn, in the order they stand.
79560
- // The screen is the viewport of the box that scrolls the list — what "150%"
79561
- // is a percentage of. A box that does not scroll — a list given no height, as
79562
- // tall as its items — shows no more of them than the browser does: its
79563
- // viewport is cut by the browser's, or a budget in % would draw items to fill
79564
- // a screen they make grow. A list off screen is met at its edge nearest to the
79565
- // screen, where it comes in.
79566
- const readWindowGeometry = (scrollerEl, listEl, horizontal) => {
79610
+ // The screen the render window is framed on, along the scroll axis and in
79611
+ // viewport coordinates: the viewport of the box that scrolls the list — what
79612
+ // "150%" is a percentage of. A box that does not scroll — a list given no
79613
+ // height, as tall as its items — shows no more of them than the browser does:
79614
+ // its viewport is cut by the browser's, or a budget in % would draw items to
79615
+ // fill a screen they make grow.
79616
+ const readWindowScreen = (scrollerEl, horizontal) => {
79567
79617
  const scrollerRect = getScrollerViewportRect(scrollerEl);
79568
- let screenFrom = horizontal ? scrollerRect.left : scrollerRect.top;
79569
- let screenTo = horizontal ? scrollerRect.right : scrollerRect.bottom;
79618
+ let from = horizontal ? scrollerRect.left : scrollerRect.top;
79619
+ let to = horizontal ? scrollerRect.right : scrollerRect.bottom;
79570
79620
  if (!canScrollerScroll(scrollerEl, horizontal ? "x" : "y")) {
79571
79621
  const browserSize = horizontal ? document.documentElement.clientWidth : document.documentElement.clientHeight;
79572
- if (screenFrom < 0) {
79573
- screenFrom = 0;
79622
+ if (from < 0) {
79623
+ from = 0;
79574
79624
  }
79575
- if (screenTo > browserSize) {
79576
- screenTo = browserSize;
79625
+ if (to > browserSize) {
79626
+ to = browserSize;
79577
79627
  }
79578
79628
  }
79629
+ return {
79630
+ from,
79631
+ to
79632
+ };
79633
+ };
79634
+ // What the render window is placed and sized on (see evaluateWindow), along
79635
+ // the scroll axis and in viewport coordinates: the size of the screen (see
79636
+ // readWindowScreen), the part of the list on it (the band), and the items
79637
+ // drawn, in the order they stand. A list off screen is met at its edge nearest
79638
+ // to the screen, where it comes in.
79639
+ const readWindowGeometry = (scrollerEl, listEl, horizontal) => {
79640
+ const {
79641
+ from: screenFrom,
79642
+ to: screenTo
79643
+ } = readWindowScreen(scrollerEl, horizontal);
79579
79644
  const screenSize = screenTo - screenFrom;
79580
79645
  if (screenSize < 1) {
79581
79646
  return null;
@@ -83478,39 +83543,29 @@ const BadgeListShrinkWrap = ({
83478
83543
  const registry = useBadgeRegistry(children, max !== undefined || fallback !== undefined);
83479
83544
  const measureRef = useRef();
83480
83545
  const visibleRef = useRef();
83546
+
83547
+ // In a closed popup the rows have no size to read: the list is measured when
83548
+ // it comes on screen, before the frame is painted.
83549
+ useDisplayedLayoutEffect(visibleRef, () => {
83550
+ shrinkWrapToWidestRow(visibleRef.current, measureRef.current);
83551
+ }, [children]);
83481
83552
  useLayoutEffect(() => {
83482
83553
  const measureEl = measureRef.current;
83483
83554
  const visibleEl = visibleRef.current;
83484
83555
  if (!measureEl || !visibleEl) {
83485
83556
  return undefined;
83486
83557
  }
83487
- let observer;
83488
- let rafId;
83489
- const measure = () => {
83490
- visibleEl.style.width = "";
83491
- // Clone the already-rendered DOM nodes instead of letting React/Preact
83492
- // render the children a second time into the ghost: re-rendering would
83493
- // instantiate Badge/Badge.Button a second time, double-registering their
83494
- // controllers (and any other mount side effect) under the same id.
83495
- measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
83496
- const optimalWidth = measureWidestChildRow(measureEl);
83497
- if (optimalWidth !== null) {
83498
- visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
83499
- }
83500
- };
83501
-
83502
- // A single badge is a single row, and a single row is already the widest
83503
- // one: there is nothing to narrow, and no width the list could be given
83504
- // that would change that.
83558
+ // A single row whatever room it is given: nothing a resize could narrow.
83505
83559
  if (visibleEl.children.length < 2) {
83506
- visibleEl.style.width = "";
83507
- measureEl.replaceChildren();
83508
83560
  return undefined;
83509
83561
  }
83510
- measure();
83562
+ let observer;
83563
+ let rafId;
83511
83564
  const onResize = () => {
83512
83565
  cancelAnimationFrame(rafId);
83513
- rafId = requestAnimationFrame(measure);
83566
+ rafId = requestAnimationFrame(() => {
83567
+ shrinkWrapToWidestRow(visibleEl, measureEl);
83568
+ });
83514
83569
  };
83515
83570
  const outerParent = measureEl.parentElement?.parentElement;
83516
83571
  if (outerParent) {
@@ -83556,6 +83611,40 @@ const BadgeListShrinkWrap = ({
83556
83611
  })
83557
83612
  );
83558
83613
  };
83614
+
83615
+ // Narrows the list to its widest row, read from clones of its badges in the
83616
+ // ghost.
83617
+ //
83618
+ // What holds the list may have sized itself around the width it had before —
83619
+ // a callout pins its body to the width it measured — so a change is announced
83620
+ // with a bubbling navi_size_change for it to measure again, in the same frame.
83621
+ const shrinkWrapToWidestRow = (visibleEl, measureEl) => {
83622
+ // Not drawn (back in a closed popup): no row to read. The width is kept for
83623
+ // the next opening, which measures again before painting.
83624
+ if (visibleEl.getClientRects().length === 0) {
83625
+ return;
83626
+ }
83627
+ const widthBefore = visibleEl.style.width;
83628
+ visibleEl.style.width = "";
83629
+ if (visibleEl.children.length < 2) {
83630
+ // A single badge is a single row, and a single row is already the widest
83631
+ // one: there is nothing to narrow.
83632
+ measureEl.replaceChildren();
83633
+ } else {
83634
+ // Clone the already-rendered DOM nodes instead of letting React/Preact
83635
+ // render the children a second time into the ghost: re-rendering would
83636
+ // instantiate Badge/Badge.Button a second time, double-registering their
83637
+ // controllers (and any other mount side effect) under the same id.
83638
+ measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
83639
+ const optimalWidth = measureWidestChildRow(measureEl);
83640
+ if (optimalWidth !== null) {
83641
+ visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
83642
+ }
83643
+ }
83644
+ if (visibleEl.style.width !== widthBefore) {
83645
+ dispatchPublicCustomEvent(visibleEl, "navi_size_change");
83646
+ }
83647
+ };
83559
83648
  const BadgeListMaxLines = ({
83560
83649
  fallback,
83561
83650
  children,