@jsenv/navi 0.29.398 → 0.29.400

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.
@@ -71,9 +71,10 @@ to start when unsure which export solves a problem.
71
71
 
72
72
  - `navigation.md` — the position of the user belongs in the URL by default,
73
73
  and that decision is not retrofittable: routes, params, search params,
74
- `<Route>`, tab rows, `navBack`, `RouteTravel`, where a navigation lands, a
75
- `SlideContainer` read from the URL, and what a layer drawn over the screen may
76
- say in its address. Read before writing any routing code.
74
+ `<Route>`, tab rows, `navBack`, `RouteTravel`, where a navigation lands (a
75
+ `#id` landing on the element it names), a `SlideContainer` read from the URL,
76
+ and what a layer drawn over the screen may say in its address. Read before
77
+ writing any routing code, and before scrolling to an element on arrival.
77
78
  - `route_transitions.md` — a transition states a relation the user reads as a
78
79
  map; a pair of routes is animated by `RouteTravel` or by a transition, never
79
80
  both; a test waits for the page arriving, never for its address. Read before
@@ -137,7 +138,9 @@ to start when unsure which export solves a problem.
137
138
  `action`, `command` + `commandFor`). Read before writing any handler whose
138
139
  body only assigns state.
139
140
  - `control_value.md` — who holds a control's value; `--navi-update` for a
140
- button proposing a value; empty keeps the shape of the question; what a
141
+ button proposing a value; a `defaultValue` follows the record it was read
142
+ from (no `key` to remount); drawing what a picker holds (`ui={Component}`,
143
+ not a signal of your own); empty keeps the shape of the question; what a
141
144
  `stateSignal` brings; a time of day typed (`TimeSpin`) or turned
142
145
  (`TimeWheel`). Read before wiring a value with `value` + `uiAction`.
143
146
  - `control_object.md` — one value made of several controls: `ControlGroup`
@@ -259,6 +262,9 @@ to start when unsure which export solves a problem.
259
262
  - **Routing is signal-based**: URL state (including search params) two-way
260
263
  syncs with signals. Don't build parallel state for what a route/query
261
264
  signal already tracks.
265
+ - **A link lands on an element by its fragment**: give the element an `id` and
266
+ link to `#id`. Never a search param plus `scrollIntoView` in an effect (see
267
+ `navigation.md`).
262
268
  - **Actions** model async operations with a lifecycle (idle, running,
263
269
  completed, failed, aborted). Components read an action's state via
264
270
  `useAsyncData`, not by manually tracking loading/error booleans. Reading does
@@ -10,6 +10,8 @@ somewhere else in the app.
10
10
  - [A button that proposes a value is `--navi-update`](#a-button-that-proposes-a-value-is---navi-update)
11
11
  - [`--navi-update:smooth`: the control is seen answering](#--navi-updatesmooth-the-control-is-seen-answering)
12
12
  - [`signal` + `defaultValue`: the answer and where it starts](#signal--defaultvalue-the-answer-and-where-it-starts)
13
+ - [A `defaultValue` follows what it was read from](#a-defaultvalue-follows-what-it-was-read-from)
14
+ - [Drawing what a control holds](#drawing-what-a-control-holds)
13
15
  - [What a signal holds, control by control](#what-a-signal-holds-control-by-control)
14
16
  - [Empty keeps the shape of the question](#empty-keeps-the-shape-of-the-question)
15
17
  - [Which controls take a `signal`](#which-controls-take-a-signal)
@@ -22,12 +24,12 @@ somewhere else in the app.
22
24
 
23
25
  ## The three answers
24
26
 
25
- | what you pass | who holds the value | when the user acts |
26
- | ---------------------- | ------------------- | ------------------------------------------- |
27
- | nothing | the control | it keeps it; `uiAction` tells you |
28
- | `defaultValue` | the control | same — the default is only where it starts |
29
- | `signal` | the signal | the control writes it back, both ways |
30
- | `value` (or `checked`) | you | nothing moves until you hand a new one down |
27
+ | what you pass | who holds the value | when the user acts |
28
+ | ---------------------- | ------------------- | ------------------------------------------------------------------------------------- |
29
+ | nothing | the control | it keeps it; `uiAction` tells you |
30
+ | `defaultValue` | the control | same — the default is where it starts, and a new one is taken while nothing is edited |
31
+ | `signal` | the signal | the control writes it back, both ways |
32
+ | `value` (or `checked`) | you | nothing moves until you hand a new one down |
31
33
 
32
34
  A control given `value` and nothing to listen to it (`uiAction`, `action`, a
33
35
  `signal`, a surrounding form) is read-only, and says so in dev: it is showing
@@ -228,6 +230,50 @@ default rather than leaving it blank — which is what makes "nothing decided
228
230
  here, use the usual answer" expressible at all. Without a `defaultValue`, an
229
231
  emptied signal empties the control.
230
232
 
233
+ ## A `defaultValue` follows what it was read from
234
+
235
+ A field editing a record starts on that record, `defaultValue={user.first_name}`,
236
+ and the record moves after mount: the save comes back normalized (a trailing
237
+ space trimmed), another screen or device changed it, a refresh arrived. The
238
+ control takes the new default **while it holds no edit** — nothing typed, picked
239
+ or toggled since the value the outside last accepted. An edit is never undone:
240
+ the default waits, and lands once that edit is accepted (the action succeeded)
241
+ or taken back (the action failed, the popup was cancelled).
242
+
243
+ This holds for a control, for a group given a `defaultValue` (a selectable list,
244
+ a `ControlGroup`), and for everything inside a picker's popup, which stays
245
+ mounted while closed. So the record goes in as `defaultValue` and nothing
246
+ else: no `key` to remount the popup when it changes. A key is worse than
247
+ unnecessary here, since it also destroys whatever is being typed when the
248
+ answer to ANOTHER field arrives.
249
+
250
+ A picker's cancel follows the same line. It puts back what the picker held at
251
+ open, which undoes the person's edits, but what the outside moved during the
252
+ opening stays moved.
253
+
254
+ ## Drawing what a control holds
255
+
256
+ A picker's trigger usually shows the choice (a label for a code, a summary of
257
+ an object). Read it from the picker rather than keeping a copy:
258
+
259
+ ```jsx
260
+ const VisibilityLabel = ({ value }) => <Text>{labelOf(value)}</Text>;
261
+
262
+ <Picker name="visibility" defaultValue={user.visibility} ui={VisibilityLabel}>
263
+ …
264
+ </Picker>;
265
+ ```
266
+
267
+ `ui={Component}` is handed `value`, `loading` and `interactive`. A `ui` given as
268
+ an element reads the same three with `usePickerState()`.
269
+
270
+ What not to write: `const s = useSignal(defaultValue)` bound as `signal={s}`,
271
+ just to have something to read. `useSignal` reads its argument once, so the
272
+ signal never hears the record move, and a bound signal takes precedence over the
273
+ control's own `defaultValue`. The field is stuck on its first value. A signal is
274
+ for a value the app owns and writes itself (a url param, state shared between
275
+ screens), not a mirror kept for drawing.
276
+
231
277
  ## What a signal holds, control by control
232
278
 
233
279
  The signal holds what the control is ABOUT, which is not always its `value`
@@ -59,6 +59,10 @@ and are referenced from here rather than restated.
59
59
  because the picture alone decided. Only a release at rest, or a slow retreat,
60
60
  is judged by the picture — about a third of a box — because there the
61
61
  position is the only witness left.
62
+ - **A travel caught in flight and thrown on the way it was going asks for the
63
+ one after.** Two swipes the same way are two screens, however close
64
+ together: the second one is not a vote for the screen already arriving. See
65
+ [below](#a-hand-reaching-for-something-still-moving-is-reaching-for-that-thing).
62
66
  - **The bars those verdicts compare against sit well below the speed the
63
67
  fingertip actually has, and they must.** Velocity is averaged over a trailing
64
68
  window (pointer events arrive irregularly, and the last one before a release
@@ -449,6 +453,26 @@ Taking over means the pictures stop where they are and answer the finger again,
449
453
  from where they stand (`slack`) rather than from zero. Only one box is in hand,
450
454
  and walking out of either of its ends is a travel of its own — see below.
451
455
 
456
+ What letting go of it again means is read from the hand, three ways:
457
+
458
+ - **merely touched** — a press that moved less than a gesture's worth: nobody
459
+ asked the travel to stop, it was asked to wait, and it carries on to where it
460
+ was going;
461
+ - **thrown back** — against the way it was going, fast: everything goes back;
462
+ - **thrown on** — the way it was already going, fast, having moved by more than
463
+ a tremor: that is a second push, and it asks for the screen AFTER the one
464
+ arriving (`thrownOn`). Read as a verdict on the box in hand, it only confirms
465
+ what was already arriving — the travel stops at the press and goes on to the
466
+ same place, and the hand feels its second swipe swallowed. So the gesture
467
+ asks for the next box at the release (`onEdge` with `thrown`), with the
468
+ picture still short of the end of the one in hand, and nothing on screen
469
+ moves for it: the travel goes on from where the hand left it, to one screen
470
+ further. It is the wheel's rule (a second push over the tail is a screen, not
471
+ credit towards one) said for a hand.
472
+
473
+ A slow hand going the same way and stopping before it lets go is not thrown:
474
+ there the picture decides, and the screen arriving arrives.
475
+
452
476
  ### A hand that does not stop at the end of a page is asking for the next one
453
477
 
454
478
  One travel brings in one neighbour, but a gesture is not over because a travel
@@ -470,6 +494,14 @@ The two ends cost differently, and it is worth knowing which one is being felt:
470
494
  one instead. The travel turns around where it stands, on the same transition,
471
495
  and there is no gap at all.
472
496
 
497
+ That cost is a `RouteTravel` one: a `SlideContainer` has every slide mounted,
498
+ so handing over is only re-staging — the slide reached becomes the one dragged,
499
+ the one left stays beside it, the slide past it is put on the other side, and
500
+ nothing on screen moves. Which is also why only a `SlideContainer` answers a
501
+ throw (`thrown`, above): a `RouteTravel` would have to jump its pictures the
502
+ rest of the way before handing over, a page seen leaping, so it declines and
503
+ the page caught simply arrives.
504
+
473
505
  What the browser will not turn around with it is everything ELSE the
474
506
  transition carries — see [One gesture that bar cannot
475
507
  follow](#one-gesture-that-bar-cannot-follow).
@@ -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
 
@@ -16,6 +16,7 @@ linking to them, and turning them into tabs.
16
16
  - [The back arrow: `navBack`](#the-back-arrow-navback)
17
17
  - [Tabs that travel: `RouteTravel`](#tabs-that-travel-routetravel)
18
18
  - [Where a navigation lands: the scroll](#where-a-navigation-lands-the-scroll)
19
+ - [Landing on an element: the fragment](#landing-on-an-element-the-fragment)
19
20
  - [Creating something, then editing it](#creating-something-then-editing-it)
20
21
  - [Tabs that are not routes](#tabs-that-are-not-routes)
21
22
  - [A `SlideContainer` in the URL: a position that is not a place one came from](#a-slidecontainer-in-the-url-a-position-that-is-not-a-place-one-came-from)
@@ -694,7 +695,7 @@ the two, never both: see [route_transitions.md](./route_transitions.md).
694
695
 
695
696
  ## Where a navigation lands: the scroll
696
697
 
697
- Four cases, and they are not a policy to configure but four different facts:
698
+ Five cases, and they are not a policy to configure but five different facts:
698
699
 
699
700
  - **Going somewhere new** (a `<Link>`, anything that pushes to another path)
700
701
  lands at the top. It is an arrival: the offset one had elsewhere means nothing
@@ -704,6 +705,10 @@ Four cases, and they are not a policy to configure but four different facts:
704
705
  reader is scrolled in — a [layer](#a-layer-over-the-screen-what-its-address-may-say)
705
706
  opening, a [state whose values are places](#a-state-whose-values-are-places-history-push)
706
707
  written — and moves nothing, like a replace.
708
+ - **Going somewhere new, to an element** (`/places/le-set#tournaments`) lands
709
+ on the element whose `id` is the fragment, once it is rendered — even when it
710
+ arrives with the data a second later. See
711
+ [Landing on an element](#landing-on-an-element-the-fragment).
707
712
  - **Going back or forward** (the browser's buttons, `navBack()`,
708
713
  `history.back()`) lands where that page was left. navi keeps the position and
709
714
  puts it back once the page is really rendered, which is what the browser
@@ -732,6 +737,81 @@ in a browser with no Navigation API — is `navBack()`. Where there may be
732
737
  nothing to go back to (a shared link opened cold), decide what the arrow does
733
738
  from the history, not from the link.
734
739
 
740
+ ### Landing on an element: the fragment
741
+
742
+ A link meant to bring one element under the reader's eyes — a notification
743
+ pointing at a section several screens down, a shared link to a comment, a row
744
+ in a list — is an `id` on the element and a `#id` in the link. Nothing else:
745
+
746
+ ```jsx
747
+ <Box id="tournaments">…</Box>
748
+
749
+ // anywhere else
750
+ <Link href={`${PLACE_ROUTE.buildUrl({ slug })}#tournaments`}>…</Link>
751
+ ```
752
+
753
+ `<Link route>` builds no fragment, so the link spells its `href`.
754
+
755
+ The browser alone is not enough in an app. It answers a fragment when the
756
+ document finishes loading and on a fragment navigation within the page — both
757
+ before the data has drawn the element — and a navigation navi routes is not a
758
+ fragment navigation at all. So navi answers every URL carrying a hash itself:
759
+
760
+ - **It waits for the element, and for the page.** The element is reached when
761
+ it exists and shows something (`checkVisibility`): one still loading, or
762
+ rendered inside a closed tab or a folded `<details>`, has not arrived yet.
763
+ navi opens nothing to reach it, and reaches it as soon as it shows — the
764
+ reader opening the `<details>`, switching to the tab — while the wait lasts.
765
+ And it is reached once no route or action is loading: until then, what sits
766
+ above it may still be drawn and push it down — and a page reusing the node of
767
+ the page being left (`/places/a#tournaments` → `/places/b#tournaments`) holds
768
+ it before its own data has come.
769
+ - **It gives up.** Once the document has had no route or action loading for
770
+ `graceAfterIdle` (1 s). A link to an element that is gone lands where a link
771
+ without a fragment would — at the top of a page one arrives at — with no
772
+ mark. A document that never stops working is answered after `maxWait`
773
+ (10 s), with the element as it stands.
774
+ - **A return is not an arrival.** Back, forward and a reload land where the
775
+ page was left, as they do without a fragment; the fragment then only puts
776
+ `:target` on its element — no scroll, no ring, no focus.
777
+ - **It answers once per arrival.** What is watched is the path and the hash: a
778
+ search param written while the reader is there (a filter, a page) does not
779
+ throw them back to the element. Pressing the very link one is on answers
780
+ again.
781
+ - **What the reader gets**: the element against the top edge, instantly (what
782
+ the browser does with a fragment); the keyboard focus on it when it is itself
783
+ focusable — an `id` on the link or the button, not on a box around one; and a
784
+ fading ring, the element carrying `data-url-target` for `markDuration` (2 s).
785
+ The ring is coloured by `--navi-url-target-color` and lives in `@layer navi`:
786
+ an unlayered `[data-url-target] { … }` replaces it, `animation: none` removes
787
+ it. Its length is `markDuration`, which also publishes
788
+ `--navi-url-target-duration` to CSS — setting only the variable makes the ring
789
+ and the attribute disagree.
790
+ - **Adjusting it**: `setUrlTargetOptions()`, once, at the app's start — the
791
+ alignment (`block`), `behavior`, `markDuration`, `graceAfterIdle`, `maxWait`.
792
+
793
+ **`:target` is the lasting "this one"**, and `data-url-target` the moment it
794
+ arrived. navi sets `:target` on the element as it lands on it — the browser
795
+ alone never would after a navigation navi routes, nor for an element that
796
+ arrived after the load — and takes it off once the URL stops naming the
797
+ element, which the browser alone would not either: a search param written on
798
+ the page rebuilds the address without its fragment, a back returns to the page
799
+ without one. It stays on the element navi landed on; a node rendered in its
800
+ place later is another one. In JS, `useUrlTargetId()` is the id the hash
801
+ designates, `""` when there is none, re-rendering when it changes.
802
+
803
+ #### A fragment, or a search param
804
+
805
+ A fragment says where to look; a search param says what to show. When the page
806
+ draws something differently for the value — a filter, a selected row, an open
807
+ panel — it is a [search param](#search-params). When the page is the same and
808
+ only the reader's eyes should go somewhere, it is a fragment, down to a single
809
+ row (`#tournament_42`).
810
+
811
+ A search param plus a `scrollIntoView` in an effect rebuilds this, less well:
812
+ the effect fires at mount, before what sits above the element has laid out,
813
+ and nothing ever tells it to stop waiting.
814
+
735
815
  ### A row of tabs, where a replace IS an arrival
736
816
 
737
817
  The tabs of a `<RouteTravel>` navigate by replacing, and yet each one is another
@@ -1008,7 +1008,8 @@ slots".
1008
1008
  Being open is where the user is, and what a popup draws belongs to the screen
1009
1009
  exactly the way a page's own data does. So a popup that loads something binds
1010
1010
  its open state (above) and asks for its data with a `routeAction` whose params
1011
- are `false` while it is closed:
1011
+ are `false` while it is closed — and its content reads the question itself,
1012
+ without the part that says whether it is open:
1012
1013
 
1013
1014
  ```js
1014
1015
  const groupSheetOpenSignal = stateSignal(false, {
@@ -1016,12 +1017,17 @@ const groupSheetOpenSignal = stateSignal(false, {
1016
1017
  type: "boolean",
1017
1018
  });
1018
1019
 
1020
+ // asked while the sheet is open
1019
1021
  export const GROUP_MEMBERS = routeAction(GAME_ROUTE, USER.GET_MANY, () => {
1020
1022
  if (!groupSheetOpenSignal.value) {
1021
1023
  return false;
1022
1024
  }
1023
1025
  return { group: groupSignal.value };
1024
1026
  });
1027
+ // read by the sheet, open or closed
1028
+ export const GROUP_MEMBERS_SHOWN = USER.GET_MANY.bindParams({
1029
+ group: groupSignal,
1030
+ });
1025
1031
  ```
1026
1032
 
1027
1033
  ```jsx
@@ -1030,15 +1036,16 @@ export const GROUP_MEMBERS = routeAction(GAME_ROUTE, USER.GET_MANY, () => {
1030
1036
  </Dialog>;
1031
1037
 
1032
1038
  const GroupMembers = () => {
1033
- const [members] = useAsyncData(GROUP_MEMBERS); // reads, never runs
1039
+ const [members] = useAsyncData(GROUP_MEMBERS_SHOWN); // reads, never runs
1034
1040
  …
1035
1041
  };
1036
1042
  ```
1037
1043
 
1038
1044
  The same shape when the popup is one of many — a sheet on every card, opened
1039
1045
  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:
1046
+ route action reads the id straight from it, while each card's content reads the
1047
+ question of its own card. Declared on the root route, since a card is not a
1048
+ page:
1042
1049
 
1043
1050
  ```js
1044
1051
  const seatSheetSignal = stateSignal(undefined, {
@@ -1048,18 +1055,39 @@ const seatSheetSignal = stateSignal(undefined, {
1048
1055
  });
1049
1056
  const ANY_PAGE = route("/", { searchParams: { seat: seatSheetSignal } });
1050
1057
 
1058
+ export const seatableUsersOf = (gameId) => ({ game: gameId });
1051
1059
  export const SEATABLE_USERS = routeAction(ANY_PAGE, USER.GET_MANY, () => {
1052
1060
  const gameId = seatSheetSignal.value;
1053
- return gameId ? { game: gameId } : false;
1061
+ return gameId ? seatableUsersOf(gameId) : false;
1054
1062
  });
1055
1063
  ```
1056
1064
 
1057
1065
  ```jsx
1058
1066
  <Dialog signal={seatSheetSignal} value={game.id}>
1059
- <SeatableUsers />
1060
- </Dialog>
1067
+ <SeatableUsers gameId={game.id} />
1068
+ </Dialog>;
1069
+
1070
+ const SeatableUsers = ({ gameId }) => {
1071
+ const [users] = useAsyncData(
1072
+ USER.GET_MANY.bindParams(seatableUsersOf(gameId)),
1073
+ );
1074
+ …
1075
+ };
1061
1076
  ```
1062
1077
 
1078
+ **The content reads its question, never the route action.** The route action's
1079
+ params are `false` while the popup is closed, and for the route action that is
1080
+ right: it says _when_ to ask. Read by the content, the same `false` says there
1081
+ is nothing to show — `useAsyncData` hands back `undefined` the moment the popup
1082
+ closes, the list empties while hidden, and the next opening builds it again from
1083
+ the top: the scroll position is lost, and every row is rendered once more for an
1084
+ answer that never left the store. When the popup is one of many, it is worse: the
1085
+ route action follows whichever popup is open, so a card's sheet kept mounted
1086
+ since an earlier opening redraws with another card's rows each time that one
1087
+ opens. Closing a popup does not change what it is about, so what it reads leaves
1088
+ the open state out. Equal params share one instance — the content reads the very
1089
+ one the route action runs, so nothing is asked twice and nothing is copied.
1090
+
1063
1091
  **A popup that waits holds its own `<Loading>`**, like every other part of a
1064
1092
  screen that can wait — it is not built into `Dialog`/`Popover`, because only the
1065
1093
  code inside knows whether anything in there loads at all:
@@ -1116,7 +1144,8 @@ become a popup's private request — asked for late, and asked for alone.
1116
1144
 
1117
1145
  A closed popup builds nothing: `children` are mounted on the first open, and
1118
1146
  stay mounted afterwards — a reopened popup finds its scroll position and its
1119
- half-typed form where it left them.
1147
+ half-typed form where it left them, as long as the data it draws does not leave
1148
+ with the close (see [A popup that loads data](#a-popup-that-loads-data)).
1120
1149
 
1121
1150
  "Closed" is two states, not one — never opened yet, and closed again after an
1122
1151
  opening — so the `mount` prop answers both at once:
@@ -1134,7 +1163,13 @@ off it, fields a surrounding form submits, a size measured from outside.
1134
1163
 
1135
1164
  Whatever the value but `"while-opened"`, intent on the anchor — a pointer
1136
1165
  entering it, focus landing in it — builds the content ahead of the click that
1137
- will open it.
1166
+ will open it. A `"while-opened"` popup is warmed only by a press on what opens
1167
+ it — a `--navi-open` button, a picker's trigger when a press opens it, an
1168
+ expandable's UI part — and a press that ends without opening it (a scroll, a
1169
+ drag, a release elsewhere) throws the content away: there is still one content
1170
+ at a time. A popup with an `onOpen` is never warmed, so the content is always
1171
+ built after it (see
1172
+ [`onOpen` runs before the popup has built anything](#onopen-runs-before-the-popup-has-built-anything)).
1138
1173
 
1139
1174
  `"while-opened"` is what an uncontrolled field seeded from a `defaultValue`
1140
1175
  needs: without it, a popup reopened after the underlying value changed still
package/docs/scroll.md CHANGED
@@ -3,9 +3,9 @@
3
3
  Where scrolling happens in a navi app, and how the pieces that live inside a
4
4
  scrolling area (`Box header/body/footer`, `List`, a popup) are told about it.
5
5
 
6
- Where a navigation LANDS is another subject — a push at the top, a back or
7
- forward where the page was left, a row of tabs giving each tab back the offset
8
- it was read at: see
6
+ Where a navigation LANDS is another subject — a push at the top, a push with a
7
+ `#id` on the element it names, a back or forward where the page was left, a row
8
+ of tabs giving each tab back the offset it was read at: see
9
9
  [navigation.md](./navigation.md#where-a-navigation-lands-the-scroll).
10
10
 
11
11
  - [What makes header/body/footer work: the overflow](#what-makes-headerbodyfooter-work-the-overflow)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.398",
3
+ "version": "0.29.400",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "preact": "^11.0.0-rc.2"
35
35
  },
36
36
  "dependencies": {
37
- "@jsenv/dom": "0.17.75",
37
+ "@jsenv/dom": "0.17.76",
38
38
  "@jsenv/humanize": "1.8.5",
39
39
  "@jsenv/validity": "0.4.5"
40
40
  },