@jsenv/navi 0.29.398 → 0.29.399

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.
@@ -146,8 +146,9 @@ by the code arriving.
146
146
  `prefetch={false}` opts one out. The url is matched against the routes on
147
147
  screen, a section and the page inside it alike, and what is prerun is **every
148
148
  route action that asks nothing of the address** — a page's code, a read
149
- without params. An action whose params come from the address is never
150
- prefetched: a prefetch has no address, the id may still be chosen.
149
+ without params. An action whose params come from the address is not
150
+ prefetched by a link: a link's prefetch has no address, the id may still be
151
+ chosen.
151
152
  `routeAction(route, action, undefined, { prefetch: false })` keeps a
152
153
  param-less read that is not worth a hover out of it.
153
154
 
@@ -320,6 +320,12 @@ So a dialog whose content is seeded once — an uncontrolled field on a
320
320
  first render. Learning it afterwards would mean mounting on the previous subject
321
321
  and correcting it, which is a flicker at best and stale fields at worst.
322
322
 
323
+ One opening does not follow that order: a `mount="while-opened"` popup pressed on
324
+ its own `anchor` (a picker's trigger, an expandable's UI part) has its content
325
+ built by the press, ahead of the click (see
326
+ [what the popup holds while it is closed](#what-the-popup-holds-while-it-is-closed)). A popup opened ON something is
327
+ opened by a command pressed elsewhere, and keeps the order above.
328
+
323
329
  The two other places one could listen are not that moment, and it is worth
324
330
  knowing why:
325
331
 
@@ -1008,7 +1014,8 @@ slots".
1008
1014
  Being open is where the user is, and what a popup draws belongs to the screen
1009
1015
  exactly the way a page's own data does. So a popup that loads something binds
1010
1016
  its open state (above) and asks for its data with a `routeAction` whose params
1011
- are `false` while it is closed:
1017
+ are `false` while it is closed — and its content reads the question itself,
1018
+ without the part that says whether it is open:
1012
1019
 
1013
1020
  ```js
1014
1021
  const groupSheetOpenSignal = stateSignal(false, {
@@ -1016,12 +1023,17 @@ const groupSheetOpenSignal = stateSignal(false, {
1016
1023
  type: "boolean",
1017
1024
  });
1018
1025
 
1026
+ // asked while the sheet is open
1019
1027
  export const GROUP_MEMBERS = routeAction(GAME_ROUTE, USER.GET_MANY, () => {
1020
1028
  if (!groupSheetOpenSignal.value) {
1021
1029
  return false;
1022
1030
  }
1023
1031
  return { group: groupSignal.value };
1024
1032
  });
1033
+ // read by the sheet, open or closed
1034
+ export const GROUP_MEMBERS_SHOWN = USER.GET_MANY.bindParams({
1035
+ group: groupSignal,
1036
+ });
1025
1037
  ```
1026
1038
 
1027
1039
  ```jsx
@@ -1030,15 +1042,16 @@ export const GROUP_MEMBERS = routeAction(GAME_ROUTE, USER.GET_MANY, () => {
1030
1042
  </Dialog>;
1031
1043
 
1032
1044
  const GroupMembers = () => {
1033
- const [members] = useAsyncData(GROUP_MEMBERS); // reads, never runs
1045
+ const [members] = useAsyncData(GROUP_MEMBERS_SHOWN); // reads, never runs
1034
1046
  …
1035
1047
  };
1036
1048
  ```
1037
1049
 
1038
1050
  The same shape when the popup is one of many — a sheet on every card, opened
1039
1051
  from wherever the card is read. Its open state then says which one, and the
1040
- route action reads the id straight from it. Declared on the root route, since a
1041
- card is not a page:
1052
+ route action reads the id straight from it, while each card's content reads the
1053
+ question of its own card. Declared on the root route, since a card is not a
1054
+ page:
1042
1055
 
1043
1056
  ```js
1044
1057
  const seatSheetSignal = stateSignal(undefined, {
@@ -1048,18 +1061,39 @@ const seatSheetSignal = stateSignal(undefined, {
1048
1061
  });
1049
1062
  const ANY_PAGE = route("/", { searchParams: { seat: seatSheetSignal } });
1050
1063
 
1064
+ export const seatableUsersOf = (gameId) => ({ game: gameId });
1051
1065
  export const SEATABLE_USERS = routeAction(ANY_PAGE, USER.GET_MANY, () => {
1052
1066
  const gameId = seatSheetSignal.value;
1053
- return gameId ? { game: gameId } : false;
1067
+ return gameId ? seatableUsersOf(gameId) : false;
1054
1068
  });
1055
1069
  ```
1056
1070
 
1057
1071
  ```jsx
1058
1072
  <Dialog signal={seatSheetSignal} value={game.id}>
1059
- <SeatableUsers />
1060
- </Dialog>
1073
+ <SeatableUsers gameId={game.id} />
1074
+ </Dialog>;
1075
+
1076
+ const SeatableUsers = ({ gameId }) => {
1077
+ const [users] = useAsyncData(
1078
+ USER.GET_MANY.bindParams(seatableUsersOf(gameId)),
1079
+ );
1080
+ …
1081
+ };
1061
1082
  ```
1062
1083
 
1084
+ **The content reads its question, never the route action.** The route action's
1085
+ params are `false` while the popup is closed, and for the route action that is
1086
+ right: it says _when_ to ask. Read by the content, the same `false` says there
1087
+ is nothing to show — `useAsyncData` hands back `undefined` the moment the popup
1088
+ closes, the list empties while hidden, and the next opening builds it again from
1089
+ the top: the scroll position is lost, and every row is rendered once more for an
1090
+ answer that never left the store. When the popup is one of many, it is worse: the
1091
+ route action follows whichever popup is open, so a card's sheet kept mounted
1092
+ since an earlier opening redraws with another card's rows each time that one
1093
+ opens. Closing a popup does not change what it is about, so what it reads leaves
1094
+ the open state out. Equal params share one instance — the content reads the very
1095
+ one the route action runs, so nothing is asked twice and nothing is copied.
1096
+
1063
1097
  **A popup that waits holds its own `<Loading>`**, like every other part of a
1064
1098
  screen that can wait — it is not built into `Dialog`/`Popover`, because only the
1065
1099
  code inside knows whether anything in there loads at all:
@@ -1116,7 +1150,8 @@ become a popup's private request — asked for late, and asked for alone.
1116
1150
 
1117
1151
  A closed popup builds nothing: `children` are mounted on the first open, and
1118
1152
  stay mounted afterwards — a reopened popup finds its scroll position and its
1119
- half-typed form where it left them.
1153
+ half-typed form where it left them, as long as the data it draws does not leave
1154
+ with the close (see [A popup that loads data](#a-popup-that-loads-data)).
1120
1155
 
1121
1156
  "Closed" is two states, not one — never opened yet, and closed again after an
1122
1157
  opening — so the `mount` prop answers both at once:
@@ -1134,7 +1169,9 @@ off it, fields a surrounding form submits, a size measured from outside.
1134
1169
 
1135
1170
  Whatever the value but `"while-opened"`, intent on the anchor — a pointer
1136
1171
  entering it, focus landing in it — builds the content ahead of the click that
1137
- will open it.
1172
+ will open it. A `"while-opened"` popup is warmed by a press on its anchor only,
1173
+ and a press that ends without opening it — a scroll, a drag, a release
1174
+ elsewhere — throws the content away: there is still one content at a time.
1138
1175
 
1139
1176
  `"while-opened"` is what an uncontrolled field seeded from a `defaultValue`
1140
1177
  needs: without it, a popup reopened after the underlying value changed still
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.398",
3
+ "version": "0.29.399",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {