@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.
@@ -18934,6 +18934,20 @@ const openCallout = (
18934
18934
  // deduped away on the grounds that the anchor did not move.
18935
18935
  callout.updatePosition = () =>
18936
18936
  positioner.update(new CustomEvent(ELEMENT_SIZE_CHANGE));
18937
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList) does
18938
+ // so after the callout measured it. The size observer positionCallout puts
18939
+ // on the message sees the change, but answers a frame later (see observeSize
18940
+ // in @jsenv/dom for why); placed again on the spot, the first painted frame
18941
+ // is the right one. Stopped here, since a popup further up holds the callout
18942
+ // out of flow and does not resize with it.
18943
+ const onContentSizeChange = (sizeChangeEvent) => {
18944
+ sizeChangeEvent.stopPropagation();
18945
+ callout.updatePosition();
18946
+ };
18947
+ calloutElement.addEventListener("navi_size_change", onContentSizeChange);
18948
+ addTeardown(() => {
18949
+ calloutElement.removeEventListener("navi_size_change", onContentSizeChange);
18950
+ });
18937
18951
 
18938
18952
  return callout;
18939
18953
  };
@@ -48054,8 +48068,8 @@ const useControlgroupProps = (props, {
48054
48068
  const [boundAction] = useActionBoundToOneParam(action, uiGroupStateController.uiStateSignal);
48055
48069
  // Mirror single-input behaviour: a controlled value with nobody listening
48056
48070
  // makes the group read-only so children don't appear interactive when they
48057
- // can't change. A form or a group around it IS someone listening — that is
48058
- // what will send the value and hand a new one back.
48071
+ // can't change. A form or a group collecting it IS someone listening — that
48072
+ // is what will send the value and hand a new one back.
48059
48073
  const listenedTo = useIsControlListenedTo(props, uiGroupStateController);
48060
48074
  const implicitReadOnlyWarnedRef = useRef(false);
48061
48075
  const implicitReadOnly = uiGroupStateController.hasValueProp && !listenedTo;
@@ -71583,6 +71597,15 @@ const useDialogProps = props => {
71583
71597
  freezeSize(dialogEl);
71584
71598
  }
71585
71599
  rectEffect.observeSize(dialogEl);
71600
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList)
71601
+ // says so, after the placement read it: placed again on the spot, where
71602
+ // observeSize answers a frame late. Stopped here, since a popup further up
71603
+ // holds this one out of flow and does not resize with it.
71604
+ const onContentSizeChange = sizeChangeEvent => {
71605
+ sizeChangeEvent.stopPropagation();
71606
+ rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
71607
+ };
71608
+ dialogEl.addEventListener("navi_size_change", onContentSizeChange);
71586
71609
  // Exposed for the placement-props effect below, which needs to re-place an
71587
71610
  // already-open dialog.
71588
71611
  repositionRef.current = repositionEvent => {
@@ -71596,6 +71619,7 @@ const useDialogProps = props => {
71596
71619
  };
71597
71620
  addCleanup(() => {
71598
71621
  repositionRef.current = null;
71622
+ dialogEl.removeEventListener("navi_size_change", onContentSizeChange);
71599
71623
  rectEffect.disconnect();
71600
71624
  });
71601
71625
  // A descendant anchored to something inside this dialog (a Callout, a
@@ -73426,6 +73450,18 @@ const usePopoverProps = props => {
73426
73450
  // while open (e.g. an expand/collapse toggle inside it) — not just when
73427
73451
  // the anchor itself moves/resizes/re-anchors.
73428
73452
  rectEffect.observeSize(popoverEl);
73453
+ // Content that sizes itself once on screen (a shrink-wrapped BadgeList)
73454
+ // says so, after the placement read it: placed again on the spot, where
73455
+ // observeSize answers a frame late. Stopped here, since a popup further up
73456
+ // holds this one out of flow and does not resize with it.
73457
+ const onContentSizeChange = sizeChangeEvent => {
73458
+ sizeChangeEvent.stopPropagation();
73459
+ rectEffect.check(new CustomEvent(ELEMENT_SIZE_CHANGE));
73460
+ };
73461
+ popoverEl.addEventListener("navi_size_change", onContentSizeChange);
73462
+ addCleanup(() => {
73463
+ popoverEl.removeEventListener("navi_size_change", onContentSizeChange);
73464
+ });
73429
73465
  // Exposed for the placement-props effect above, which needs to re-place an
73430
73466
  // already-open popover.
73431
73467
  repositionRef.current = repositionEvent => {
@@ -85079,39 +85115,29 @@ const BadgeListShrinkWrap = ({
85079
85115
  const registry = useBadgeRegistry(children, max !== undefined || fallback !== undefined);
85080
85116
  const measureRef = useRef();
85081
85117
  const visibleRef = useRef();
85118
+
85119
+ // In a closed popup the rows have no size to read: the list is measured when
85120
+ // it comes on screen, before the frame is painted.
85121
+ useDisplayedLayoutEffect(visibleRef, () => {
85122
+ shrinkWrapToWidestRow(visibleRef.current, measureRef.current);
85123
+ }, [children]);
85082
85124
  useLayoutEffect(() => {
85083
85125
  const measureEl = measureRef.current;
85084
85126
  const visibleEl = visibleRef.current;
85085
85127
  if (!measureEl || !visibleEl) {
85086
85128
  return undefined;
85087
85129
  }
85088
- let observer;
85089
- let rafId;
85090
- const measure = () => {
85091
- visibleEl.style.width = "";
85092
- // Clone the already-rendered DOM nodes instead of letting React/Preact
85093
- // render the children a second time into the ghost: re-rendering would
85094
- // instantiate Badge/Badge.Button a second time, double-registering their
85095
- // controllers (and any other mount side effect) under the same id.
85096
- measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
85097
- const optimalWidth = measureWidestChildRow(measureEl);
85098
- if (optimalWidth !== null) {
85099
- visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
85100
- }
85101
- };
85102
-
85103
- // A single badge is a single row, and a single row is already the widest
85104
- // one: there is nothing to narrow, and no width the list could be given
85105
- // that would change that.
85130
+ // A single row whatever room it is given: nothing a resize could narrow.
85106
85131
  if (visibleEl.children.length < 2) {
85107
- visibleEl.style.width = "";
85108
- measureEl.replaceChildren();
85109
85132
  return undefined;
85110
85133
  }
85111
- measure();
85134
+ let observer;
85135
+ let rafId;
85112
85136
  const onResize = () => {
85113
85137
  cancelAnimationFrame(rafId);
85114
- rafId = requestAnimationFrame(measure);
85138
+ rafId = requestAnimationFrame(() => {
85139
+ shrinkWrapToWidestRow(visibleEl, measureEl);
85140
+ });
85115
85141
  };
85116
85142
  const outerParent = measureEl.parentElement?.parentElement;
85117
85143
  if (outerParent) {
@@ -85157,6 +85183,40 @@ const BadgeListShrinkWrap = ({
85157
85183
  })
85158
85184
  );
85159
85185
  };
85186
+
85187
+ // Narrows the list to its widest row, read from clones of its badges in the
85188
+ // ghost.
85189
+ //
85190
+ // What holds the list may have sized itself around the width it had before —
85191
+ // a callout pins its body to the width it measured — so a change is announced
85192
+ // with a bubbling navi_size_change for it to measure again, in the same frame.
85193
+ const shrinkWrapToWidestRow = (visibleEl, measureEl) => {
85194
+ // Not drawn (back in a closed popup): no row to read. The width is kept for
85195
+ // the next opening, which measures again before painting.
85196
+ if (visibleEl.getClientRects().length === 0) {
85197
+ return;
85198
+ }
85199
+ const widthBefore = visibleEl.style.width;
85200
+ visibleEl.style.width = "";
85201
+ if (visibleEl.children.length < 2) {
85202
+ // A single badge is a single row, and a single row is already the widest
85203
+ // one: there is nothing to narrow.
85204
+ measureEl.replaceChildren();
85205
+ } else {
85206
+ // Clone the already-rendered DOM nodes instead of letting React/Preact
85207
+ // render the children a second time into the ghost: re-rendering would
85208
+ // instantiate Badge/Badge.Button a second time, double-registering their
85209
+ // controllers (and any other mount side effect) under the same id.
85210
+ measureEl.replaceChildren(...Array.from(visibleEl.children, child => child.cloneNode(true)));
85211
+ const optimalWidth = measureWidestChildRow(measureEl);
85212
+ if (optimalWidth !== null) {
85213
+ visibleEl.style.width = `${Math.ceil(optimalWidth)}px`;
85214
+ }
85215
+ }
85216
+ if (visibleEl.style.width !== widthBefore) {
85217
+ dispatchPublicCustomEvent(visibleEl, "navi_size_change");
85218
+ }
85219
+ };
85160
85220
  const BadgeListMaxLines = ({
85161
85221
  fallback,
85162
85222
  children,