@jsenv/navi 0.29.228 → 0.29.230

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.
@@ -64129,158 +64129,163 @@ const ListSelectable = props => {
64129
64129
  }
64130
64130
  setCurrentId(initialEl.id);
64131
64131
  }, []);
64132
- const listVnode = jsx(Next, {
64133
- "navi-selectable": "",
64134
- ...listControlRootProps,
64135
- ...listControlProps,
64136
- // "loading" is a control prop, so useControlgroupProps consumes it (into
64137
- // aria-busy / the :-navi-loading pseudo state) and it does not survive
64138
- // into the props below. ListUI needs it too — it is what makes the list
64139
- // render skeleton rows instead of its (not yet known) items.
64140
- loading: props.loading,
64141
- name: undefined,
64142
- value: undefined,
64143
- defaultValue: undefined,
64144
- selectable: undefined,
64145
- multiple: undefined,
64146
- deselectable: undefined,
64147
- focusGroupDirection: undefined,
64148
- focusGroupWrap: undefined
64149
- // Track focus inside the list: whichever item gets focus becomes current.
64150
- ,
64151
- onFocusIn: e => {
64152
- const realInput = e.target.closest("[navi-selectable-real-input]");
64153
- if (!realInput) {
64154
- return;
64155
- }
64156
- const itemEl = realInput.closest("[navi-list-item-real]");
64157
- if (itemEl && itemEl.id) {
64158
- setCurrentId(itemEl.id, e);
64159
- }
64160
- },
64161
- onnavi_request_select: e => {
64162
- const {
64163
- id
64164
- } = e.detail;
64165
- if (id === undefined) {
64166
- return;
64167
- }
64168
- const inputId = `${id}_input`;
64169
- const childController = uiGroupStateController.findChildById(inputId);
64170
- if (!childController) {
64171
- return;
64172
- }
64173
- const list = ref.current;
64174
- dispatchRequestInteraction(list, {
64175
- event: e,
64176
- name: "select",
64177
- prevented: () => e.preventDefault(),
64178
- // tell the requester that we don't want to select this item
64179
- // Asked of the item too, not only of the list: an item can be the one
64180
- // refusing (the list already holds all it accepts, see maxLength), and
64181
- // it is the one that then says why.
64182
- allowed: () => {
64183
- dispatchRequestInteraction(childController.ref.current, {
64184
- event: e,
64185
- name: "select",
64186
- prevented: () => e.preventDefault(),
64187
- allowed: () => childController.setUIState(childController.value, e)
64188
- });
64132
+
64133
+ // The group's contexts stand around the whole list, never between the <ul>
64134
+ // and the rows: the walk that gives the rows their places is over the <ul>'s
64135
+ // children, and a component sitting there is one child — every row would then
64136
+ // stand in a single slot, and reordering them (a search) would move nothing.
64137
+ const listVnode = jsx(ControlgroupChildrenWrapper, {
64138
+ ...childrenWrapperProps,
64139
+ children: jsx(Next, {
64140
+ "navi-selectable": "",
64141
+ ...listControlRootProps,
64142
+ ...listControlProps,
64143
+ // "loading" is a control prop, so useControlgroupProps consumes it (into
64144
+ // aria-busy / the :-navi-loading pseudo state) and it does not survive
64145
+ // into the props below. ListUI needs it too — it is what makes the list
64146
+ // render skeleton rows instead of its (not yet known) items.
64147
+ loading: props.loading,
64148
+ name: undefined,
64149
+ value: undefined,
64150
+ defaultValue: undefined,
64151
+ selectable: undefined,
64152
+ multiple: undefined,
64153
+ deselectable: undefined,
64154
+ focusGroupDirection: undefined,
64155
+ focusGroupWrap: undefined
64156
+ // Track focus inside the list: whichever item gets focus becomes current.
64157
+ ,
64158
+ onFocusIn: e => {
64159
+ const realInput = e.target.closest("[navi-selectable-real-input]");
64160
+ if (!realInput) {
64161
+ return;
64189
64162
  }
64190
- });
64191
- },
64192
- onnavi_request_unselect: e => {
64193
- const {
64194
- id
64195
- } = e.detail;
64196
- if (id === undefined) {
64197
- return;
64198
- }
64199
- const inputId = `${id}_input`;
64200
- const childController = uiGroupStateController.findChildById(inputId);
64201
- if (!childController) {
64202
- return;
64203
- }
64204
- const list = ref.current;
64205
- dispatchRequestInteraction(list, {
64206
- event: e,
64207
- name: "unselect",
64208
- prevented: () => e.preventDefault(),
64209
- // tell the requester that we don't want to unselect this item
64210
- allowed: () => childController.setUIState(undefined, e)
64211
- });
64212
- }
64213
- // "previous"/"next", not "up"/"down": a list is a line of items whichever
64214
- // way it is laid out, and a horizontal one walks sideways. The keys that
64215
- // drive it (arrows, Home/End) map onto that here, and so do the commands
64216
- // (--navi-previous / --navi-next / --navi-first / --navi-last).
64217
- ,
64218
- onnavi_request_nav: e => {
64219
- const {
64220
- goal
64221
- } = e.detail;
64222
- const navigableEls = getNavigableElements();
64223
- if (navigableEls.length === 0) {
64224
- return;
64163
+ const itemEl = realInput.closest("[navi-list-item-real]");
64164
+ if (itemEl && itemEl.id) {
64165
+ setCurrentId(itemEl.id, e);
64166
+ }
64167
+ },
64168
+ onnavi_request_select: e => {
64169
+ const {
64170
+ id
64171
+ } = e.detail;
64172
+ if (id === undefined) {
64173
+ return;
64174
+ }
64175
+ const inputId = `${id}_input`;
64176
+ const childController = uiGroupStateController.findChildById(inputId);
64177
+ if (!childController) {
64178
+ return;
64179
+ }
64180
+ const list = ref.current;
64181
+ dispatchRequestInteraction(list, {
64182
+ event: e,
64183
+ name: "select",
64184
+ prevented: () => e.preventDefault(),
64185
+ // tell the requester that we don't want to select this item
64186
+ // Asked of the item too, not only of the list: an item can be the one
64187
+ // refusing (the list already holds all it accepts, see maxLength), and
64188
+ // it is the one that then says why.
64189
+ allowed: () => {
64190
+ dispatchRequestInteraction(childController.ref.current, {
64191
+ event: e,
64192
+ name: "select",
64193
+ prevented: () => e.preventDefault(),
64194
+ allowed: () => childController.setUIState(childController.value, e)
64195
+ });
64196
+ }
64197
+ });
64198
+ },
64199
+ onnavi_request_unselect: e => {
64200
+ const {
64201
+ id
64202
+ } = e.detail;
64203
+ if (id === undefined) {
64204
+ return;
64205
+ }
64206
+ const inputId = `${id}_input`;
64207
+ const childController = uiGroupStateController.findChildById(inputId);
64208
+ if (!childController) {
64209
+ return;
64210
+ }
64211
+ const list = ref.current;
64212
+ dispatchRequestInteraction(list, {
64213
+ event: e,
64214
+ name: "unselect",
64215
+ prevented: () => e.preventDefault(),
64216
+ // tell the requester that we don't want to unselect this item
64217
+ allowed: () => childController.setUIState(undefined, e)
64218
+ });
64225
64219
  }
64226
- const currentId = currentIdRef.current;
64227
- let currentIndex = -1;
64228
- if (currentId) {
64229
- currentIndex = navigableEls.findIndex(el => el.id === currentId);
64230
- }
64231
- let targetEl;
64232
- if (goal === "first") {
64233
- targetEl = navigableEls[0];
64234
- } else if (goal === "last") {
64235
- targetEl = navigableEls[navigableEls.length - 1];
64236
- } else if (goal === "next") {
64237
- if (currentIndex === -1) {
64220
+ // "previous"/"next", not "up"/"down": a list is a line of items whichever
64221
+ // way it is laid out, and a horizontal one walks sideways. The keys that
64222
+ // drive it (arrows, Home/End) map onto that here, and so do the commands
64223
+ // (--navi-previous / --navi-next / --navi-first / --navi-last).
64224
+ ,
64225
+ onnavi_request_nav: e => {
64226
+ const {
64227
+ goal
64228
+ } = e.detail;
64229
+ const navigableEls = getNavigableElements();
64230
+ if (navigableEls.length === 0) {
64231
+ return;
64232
+ }
64233
+ const currentId = currentIdRef.current;
64234
+ let currentIndex = -1;
64235
+ if (currentId) {
64236
+ currentIndex = navigableEls.findIndex(el => el.id === currentId);
64237
+ }
64238
+ let targetEl;
64239
+ if (goal === "first") {
64238
64240
  targetEl = navigableEls[0];
64239
- } else if (currentIndex < navigableEls.length - 1) {
64240
- targetEl = navigableEls[currentIndex + 1];
64241
- } else {
64241
+ } else if (goal === "last") {
64242
64242
  targetEl = navigableEls[navigableEls.length - 1];
64243
+ } else if (goal === "next") {
64244
+ if (currentIndex === -1) {
64245
+ targetEl = navigableEls[0];
64246
+ } else if (currentIndex < navigableEls.length - 1) {
64247
+ targetEl = navigableEls[currentIndex + 1];
64248
+ } else {
64249
+ targetEl = navigableEls[navigableEls.length - 1];
64250
+ }
64251
+ } else if (goal === "previous") {
64252
+ if (currentIndex === -1) {
64253
+ targetEl = navigableEls[0];
64254
+ } else if (currentIndex > 0) {
64255
+ targetEl = navigableEls[currentIndex - 1];
64256
+ } else {
64257
+ targetEl = navigableEls[0];
64258
+ }
64243
64259
  }
64244
- } else if (goal === "previous") {
64245
- if (currentIndex === -1) {
64246
- targetEl = navigableEls[0];
64247
- } else if (currentIndex > 0) {
64248
- targetEl = navigableEls[currentIndex - 1];
64249
- } else {
64250
- targetEl = navigableEls[0];
64260
+ if (!targetEl) {
64261
+ return;
64251
64262
  }
64252
- }
64253
- if (!targetEl) {
64254
- return;
64255
- }
64256
- setCurrentId(targetEl.id, e);
64257
- dispatchCustomEvent(ref.current, "navi_request_scroll", {
64258
- event: e,
64259
- id: targetEl.id
64260
- });
64261
- },
64262
- onnavi_request_activate: e => {
64263
- const currentId = currentIdRef.current;
64264
- if (!currentId) {
64265
- return;
64266
- }
64267
- if (multiple || deselectable) {
64268
- const inputId = `${currentId}_input`;
64269
- const childController = uiGroupStateController.findChildById(inputId);
64270
- const isSelected = childController && childController.uiState;
64271
- dispatchCustomEvent(ref.current, isSelected ? "navi_request_unselect" : "navi_request_select", {
64263
+ setCurrentId(targetEl.id, e);
64264
+ dispatchCustomEvent(ref.current, "navi_request_scroll", {
64265
+ event: e,
64266
+ id: targetEl.id
64267
+ });
64268
+ },
64269
+ onnavi_request_activate: e => {
64270
+ const currentId = currentIdRef.current;
64271
+ if (!currentId) {
64272
+ return;
64273
+ }
64274
+ if (multiple || deselectable) {
64275
+ const inputId = `${currentId}_input`;
64276
+ const childController = uiGroupStateController.findChildById(inputId);
64277
+ const isSelected = childController && childController.uiState;
64278
+ dispatchCustomEvent(ref.current, isSelected ? "navi_request_unselect" : "navi_request_select", {
64279
+ event: e,
64280
+ id: currentId
64281
+ });
64282
+ return;
64283
+ }
64284
+ dispatchCustomEvent(ref.current, "navi_request_select", {
64272
64285
  event: e,
64273
64286
  id: currentId
64274
64287
  });
64275
- return;
64276
- }
64277
- dispatchCustomEvent(ref.current, "navi_request_select", {
64278
- event: e,
64279
- id: currentId
64280
- });
64281
- },
64282
- children: jsx(ControlgroupChildrenWrapper, {
64283
- ...childrenWrapperProps,
64288
+ },
64284
64289
  children: props.children
64285
64290
  })
64286
64291
  });
@@ -65660,52 +65665,59 @@ const ListContent = ({
65660
65665
  children
65661
65666
  }) => {
65662
65667
  const listProps = useContext(BoxForwardedPropsContext);
65663
- return jsx(Box, {
65664
- className: "navi_list_scroll_container",
65665
- overflow: overflow,
65666
- overflowX: overflowX,
65667
- overflowY: overflowY,
65668
- ...scrollBoxPaddingProps,
65669
- children: jsx(UnorderedList, {
65670
- role: role,
65671
- fallback: fallback,
65672
- fallbackShown: fallbackShown,
65673
- searchFallback: searchFallback,
65674
- searchFallbackShown: searchFallbackShown,
65675
- loadingPlaceholderShown: loadingPlaceholderShown,
65676
- error: error,
65677
- searchNoMatchMode: searchNoMatchMode,
65678
- separator: separator,
65679
- itemTransition: itemTransition,
65680
- expandX: expandX
65681
- // Deliberately not expandY here (unlike expandX above): the outer
65682
- // .navi_list_container already gets its own expandY treatment (see
65683
- // ListUI's own Box above) to fill whatever space its *own* parent
65684
- // gives it (e.g. a flex-column ancestor's flex-grow) — the <ul>
65685
- // itself must stay auto-height regardless, or it gets capped to
65686
- // match .navi_list_scroll_container's own (possibly much smaller)
65687
- // flex-resolved height instead of its real content height. That
65688
- // breaks two things at once: virtual scroll's own filler sizing
65689
- // (nothing to overflow into the scroll container in the first
65690
- // place) and any sticky List.Item header/footer inside it (their
65691
- // sticky "containing block" — the <ul>'s own box — would be
65692
- // artificially small, so they run out of room to stay stuck well
65693
- // before the user has actually scrolled through all the content).
65694
- ,
65695
- horizontal: horizontal,
65696
- spacing: spacing,
65697
- columns: columns,
65698
- itemColumns: itemColumns,
65699
- ...listProps,
65700
- tracker: tracker,
65701
- renderWindow: renderWindow,
65702
- virtual: virtual,
65703
- children: jsx(PendingScrollRefContext.Provider, {
65704
- value: pendingScrollRef,
65705
- children: children
65668
+ return (
65669
+ // Every provider the list puts around its items stands OUTSIDE the <ul>:
65670
+ // the walk that gives the rows their places (see ListDeclaredChildren) is
65671
+ // over the <ul>'s children, and one component between it and the caller's
65672
+ // rows is one child — all the rows would then stand in a single slot, and
65673
+ // reordering them would move nothing.
65674
+ jsx(PendingScrollRefContext.Provider, {
65675
+ value: pendingScrollRef,
65676
+ children: jsx(Box, {
65677
+ className: "navi_list_scroll_container",
65678
+ overflow: overflow,
65679
+ overflowX: overflowX,
65680
+ overflowY: overflowY,
65681
+ ...scrollBoxPaddingProps,
65682
+ children: jsx(UnorderedList, {
65683
+ role: role,
65684
+ fallback: fallback,
65685
+ fallbackShown: fallbackShown,
65686
+ searchFallback: searchFallback,
65687
+ searchFallbackShown: searchFallbackShown,
65688
+ loadingPlaceholderShown: loadingPlaceholderShown,
65689
+ error: error,
65690
+ searchNoMatchMode: searchNoMatchMode,
65691
+ separator: separator,
65692
+ itemTransition: itemTransition,
65693
+ expandX: expandX
65694
+ // Deliberately not expandY here (unlike expandX above): the outer
65695
+ // .navi_list_container already gets its own expandY treatment (see
65696
+ // ListUI's own Box above) to fill whatever space its *own* parent
65697
+ // gives it (e.g. a flex-column ancestor's flex-grow) — the <ul>
65698
+ // itself must stay auto-height regardless, or it gets capped to
65699
+ // match .navi_list_scroll_container's own (possibly much smaller)
65700
+ // flex-resolved height instead of its real content height. That
65701
+ // breaks two things at once: virtual scroll's own filler sizing
65702
+ // (nothing to overflow into the scroll container in the first
65703
+ // place) and any sticky List.Item header/footer inside it (their
65704
+ // sticky "containing block" — the <ul>'s own box — would be
65705
+ // artificially small, so they run out of room to stay stuck well
65706
+ // before the user has actually scrolled through all the content).
65707
+ ,
65708
+ horizontal: horizontal,
65709
+ spacing: spacing,
65710
+ columns: columns,
65711
+ itemColumns: itemColumns,
65712
+ ...listProps,
65713
+ tracker: tracker,
65714
+ renderWindow: renderWindow,
65715
+ virtual: virtual,
65716
+ children: children
65717
+ })
65706
65718
  })
65707
65719
  })
65708
- });
65720
+ );
65709
65721
  };
65710
65722
  // Where the items sit is a question about the track (the <ul>), not about the
65711
65723
  // frame around it: the frame's only child is the scroll box, which fills it and
@@ -65900,6 +65912,10 @@ const useListScrollSync = ({
65900
65912
  const block = blockRequested || (event.type === "navi_displayed" ? "center" : "nearest");
65901
65913
  const scrollToItemCall = `${getElementSignature(itemEl)}.scrollIntoView({ block: "${block}", container: "nearest" })`;
65902
65914
  debugScroll(`${trigger} -> ${scrollToItemCall}`);
65915
+ // The list is going somewhere on purpose, so there is no view to hold
65916
+ // still any more: an anchor captured before this drop it, or it would
65917
+ // put the list back where it was the moment the rows move under it.
65918
+ anchorRef.current = null;
65903
65919
  scrollIntoViewScoped(itemEl, {
65904
65920
  container: getScroller(),
65905
65921
  block
@@ -65910,17 +65926,14 @@ const useListScrollSync = ({
65910
65926
  item
65911
65927
  });
65912
65928
  };
65913
- const {
65914
- start,
65915
- end
65916
- } = renderWindowRef.current;
65917
- const isInWindow = index >= start && index < end;
65918
- if (isInWindow) {
65919
- const itemEl = findRowElement(getListEl(), item.id);
65920
- if (itemEl) {
65921
- scrollItemIntoView(itemEl);
65922
- return;
65923
- }
65929
+
65930
+ // Whether the row is drawn is asked of the dom, not of the render window:
65931
+ // the window says what a run draws, and a list whose rows are declared one
65932
+ // by one has them all in the dom whatever the window says.
65933
+ const itemEl = findRowElement(getListEl(), item.id);
65934
+ if (itemEl) {
65935
+ scrollItemIntoView(itemEl);
65936
+ return;
65924
65937
  }
65925
65938
  // Not in DOM — shift the render window. The item will read
65926
65939
  // pendingScrollRef on mount and scroll into view.
@@ -66042,9 +66055,8 @@ const useListScrollSync = ({
66042
66055
  }, []);
66043
66056
  // Watch scores of the top renderBudget items.
66044
66057
  // When scores change during an active search, scroll to top to reveal the most relevant items.
66045
- // When search becomes empty, restore the scroll position from before the search started.
66046
- // We save the first-visible item ID so restoration is item-precise
66047
- // and survives render-window shifts or item reordering.
66058
+ // When search becomes empty, put the list back at the offset (and the render
66059
+ // window) it was at when the search started — see docs/scroll.md.
66048
66060
 
66049
66061
  // NOTE POUR LE JOUR OU ON A LE MULTISELECT:
66050
66062
  // Lorsqu'on selectionne quelque chose pendant une recherche, alors ensuite meme si on clear
@@ -68001,6 +68013,28 @@ const createListVirtual = () => {
68001
68013
  } else {
68002
68014
  ownerIdsBySlot.set(slotId, [ownerId]);
68003
68015
  }
68016
+ warnIfEveryRowInOneSlot(slotId);
68017
+ };
68018
+ // Rows that all stand in the same slot keep the order they first mounted in:
68019
+ // the walk is over the children the list is given, and a component holding
68020
+ // them is one child however many rows it renders. Everything about a place
68021
+ // then stops following what the caller writes — a search reordering the rows
68022
+ // moves nothing. Said once, and only for the shape that can be nothing else:
68023
+ // the list's whole content is one child, and several rows came out of it.
68024
+ let everyRowInOneSlotWarned = false;
68025
+ const warnIfEveryRowInOneSlot = slotId => {
68026
+ if (everyRowInOneSlotWarned) {
68027
+ return;
68028
+ }
68029
+ const rootSlotIds = slotIdsByParent.get(null);
68030
+ if (!rootSlotIds || rootSlotIds.length !== 1 || rootSlotIds[0] !== slotId) {
68031
+ return;
68032
+ }
68033
+ if (ownerIdsBySlot.get(slotId).length < 2) {
68034
+ return;
68035
+ }
68036
+ everyRowInOneSlotWarned = true;
68037
+ console.warn(`List: every row stands in the same slot, so they keep the order they first rendered in — reordering them (a search, a sort) will not move them. The list's rows must be its own children: give it the rows (or a <List.Items>), not a component rendering them.`);
68004
68038
  };
68005
68039
  const removeFromSlot = (slotId, ownerId) => {
68006
68040
  const ownerIds = ownerIdsBySlot.get(slotId);