@jsenv/navi 0.29.473 → 0.29.474

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
  };
@@ -46870,8 +46884,8 @@ const useControlgroupProps = (props, {
46870
46884
  const [boundAction] = useActionBoundToOneParam(action, uiGroupStateController.uiStateSignal);
46871
46885
  // Mirror single-input behaviour: a controlled value with nobody listening
46872
46886
  // 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.
46887
+ // can't change. A form or a group collecting it IS someone listening — that
46888
+ // is what will send the value and hand a new one back.
46875
46889
  const listenedTo = useIsControlListenedTo(props, uiGroupStateController);
46876
46890
  useRef(false);
46877
46891
  const implicitReadOnly = uiGroupStateController.hasValueProp && !listenedTo;
@@ -70210,6 +70224,15 @@ const useDialogProps = props => {
70210
70224
  freezeSize(dialogEl);
70211
70225
  }
70212
70226
  rectEffect.observeSize(dialogEl);
70227
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList)
70228
+ // says so, after the placement read it: placed again on the spot, where
70229
+ // observeSize answers a frame late. Stopped here, since a popup further up
70230
+ // holds this one out of flow and does not resize with it.
70231
+ const onContentSizeChange = sizeChangeEvent => {
70232
+ sizeChangeEvent.stopPropagation();
70233
+ rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
70234
+ };
70235
+ dialogEl.addEventListener("navi_size_change", onContentSizeChange);
70213
70236
  // Exposed for the placement-props effect below, which needs to re-place an
70214
70237
  // already-open dialog.
70215
70238
  repositionRef.current = repositionEvent => {
@@ -70223,6 +70246,7 @@ const useDialogProps = props => {
70223
70246
  };
70224
70247
  addCleanup(() => {
70225
70248
  repositionRef.current = null;
70249
+ dialogEl.removeEventListener("navi_size_change", onContentSizeChange);
70226
70250
  rectEffect.disconnect();
70227
70251
  });
70228
70252
  // A descendant anchored to something inside this dialog (a Callout, a
@@ -72015,6 +72039,18 @@ const usePopoverProps = props => {
72015
72039
  // while open (e.g. an expand/collapse toggle inside it) — not just when
72016
72040
  // the anchor itself moves/resizes/re-anchors.
72017
72041
  rectEffect.observeSize(popoverEl);
72042
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList)
72043
+ // says so, after the placement read it: placed again on the spot, where
72044
+ // observeSize answers a frame late. Stopped here, since a popup further up
72045
+ // holds this one out of flow and does not resize with it.
72046
+ const onContentSizeChange = sizeChangeEvent => {
72047
+ sizeChangeEvent.stopPropagation();
72048
+ rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
72049
+ };
72050
+ popoverEl.addEventListener("navi_size_change", onContentSizeChange);
72051
+ addCleanup(() => {
72052
+ popoverEl.removeEventListener("navi_size_change", onContentSizeChange);
72053
+ });
72018
72054
  // Exposed for the placement-props effect above, which needs to re-place an
72019
72055
  // already-open popover.
72020
72056
  repositionRef.current = repositionEvent => {
@@ -83478,39 +83514,29 @@ const BadgeListShrinkWrap = ({
83478
83514
  const registry = useBadgeRegistry(children, max !== undefined || fallback !== undefined);
83479
83515
  const measureRef = useRef();
83480
83516
  const visibleRef = useRef();
83517
+
83518
+ // In a closed popup the rows have no size to read: the list is measured when
83519
+ // it comes on screen, before the frame is painted.
83520
+ useDisplayedLayoutEffect(visibleRef, () => {
83521
+ shrinkWrapToWidestRow(visibleRef.current, measureRef.current);
83522
+ }, [children]);
83481
83523
  useLayoutEffect(() => {
83482
83524
  const measureEl = measureRef.current;
83483
83525
  const visibleEl = visibleRef.current;
83484
83526
  if (!measureEl || !visibleEl) {
83485
83527
  return undefined;
83486
83528
  }
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.
83529
+ // A single row whatever room it is given: nothing a resize could narrow.
83505
83530
  if (visibleEl.children.length < 2) {
83506
- visibleEl.style.width = "";
83507
- measureEl.replaceChildren();
83508
83531
  return undefined;
83509
83532
  }
83510
- measure();
83533
+ let observer;
83534
+ let rafId;
83511
83535
  const onResize = () => {
83512
83536
  cancelAnimationFrame(rafId);
83513
- rafId = requestAnimationFrame(measure);
83537
+ rafId = requestAnimationFrame(() => {
83538
+ shrinkWrapToWidestRow(visibleEl, measureEl);
83539
+ });
83514
83540
  };
83515
83541
  const outerParent = measureEl.parentElement?.parentElement;
83516
83542
  if (outerParent) {
@@ -83556,6 +83582,40 @@ const BadgeListShrinkWrap = ({
83556
83582
  })
83557
83583
  );
83558
83584
  };
83585
+
83586
+ // Narrows the list to its widest row, read from clones of its badges in the
83587
+ // ghost.
83588
+ //
83589
+ // What holds the list may have sized itself around the width it had before —
83590
+ // a callout pins its body to the width it measured — so a change is announced
83591
+ // with a bubbling navi_size_change for it to measure again, in the same frame.
83592
+ const shrinkWrapToWidestRow = (visibleEl, measureEl) => {
83593
+ // Not drawn (back in a closed popup): no row to read. The width is kept for
83594
+ // the next opening, which measures again before painting.
83595
+ if (visibleEl.getClientRects().length === 0) {
83596
+ return;
83597
+ }
83598
+ const widthBefore = visibleEl.style.width;
83599
+ visibleEl.style.width = "";
83600
+ if (visibleEl.children.length < 2) {
83601
+ // A single badge is a single row, and a single row is already the widest
83602
+ // one: there is nothing to narrow.
83603
+ measureEl.replaceChildren();
83604
+ } else {
83605
+ // Clone the already-rendered DOM nodes instead of letting React/Preact
83606
+ // render the children a second time into the ghost: re-rendering would
83607
+ // instantiate Badge/Badge.Button a second time, double-registering their
83608
+ // controllers (and any other mount side effect) under the same id.
83609
+ measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
83610
+ const optimalWidth = measureWidestChildRow(measureEl);
83611
+ if (optimalWidth !== null) {
83612
+ visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
83613
+ }
83614
+ }
83615
+ if (visibleEl.style.width !== widthBefore) {
83616
+ dispatchPublicCustomEvent(visibleEl, "navi_size_change");
83617
+ }
83618
+ };
83559
83619
  const BadgeListMaxLines = ({
83560
83620
  fallback,
83561
83621
  children,