@jsenv/navi 0.29.141 → 0.29.143

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.
@@ -153,6 +153,16 @@ consistency across the app, not from any single call site.
153
153
  given a `ui` draws no placeholder of its own. Read it before putting a
154
154
  `BadgeList` in a picker's `ui`, before wrapping its fallback in a `Badge`,
155
155
  and before setting `maxLines` on the list rather than on the picker.
156
+ - `docs/state_binding.md` — the rule the whole API rests on: state navi shows is
157
+ BOUND (`signal`), not copied back by a callback. The three shapes an
158
+ application ever has to say (`signal` / `action` / `command` + `commandFor`),
159
+ the four ways the same mistake is written (`onCurrentChange`, `uiAction`,
160
+ `onClick` and `onOpen`/`onClose` all writing a signal), why a binding is not
161
+ merely shorter (it is written by every gesture, it moves the component when
162
+ written, and it goes through the interaction gate), what is left for the
163
+ callback, and what to conclude when the state being wired has no prop for it —
164
+ a parallel copy, or a binding navi is missing. Read it before writing any
165
+ handler whose body only assigns state.
156
166
  - `docs/control_value.md` — who holds a control's value: nobody, a bound
157
167
  `signal` (two-way, in both directions), or you (`value`/`checked`). What
158
168
  `signal` + `defaultValue` says, what a signal holds for each kind of control,
@@ -282,9 +292,13 @@ consistency across the app, not from any single call site.
282
292
  is given back the offset it was read at, and only when the row owns the
283
293
  document scroll), a back arrow that stays inside the app (`useCanNavBack()` /
284
294
  `navBack({ fallback })`), and the few cases where tabs are legitimately not
285
- URLs. Read it before writing any routing code — the
286
- position of the user belongs in the URL by default, and that decision is
287
- not retrofittable.
295
+ URLs. It also holds the middle answer — a position READ from the URL and
296
+ restored on reload without a route and without an entry per step: a search
297
+ param signal handed to a `SlideContainer`, walked to rather than jumped to,
298
+ and `history: "push"` for the states whose values ARE places (with the write
299
+ that says otherwise, `signal.set(v, { history })`). Read it before writing any
300
+ routing code — the position of the user belongs in the URL by default, and
301
+ that decision is not retrofittable.
288
302
  - Source code and demos on GitHub:
289
303
  https://github.com/jsenv/core/tree/main/packages/frontend/navi — where to go
290
304
  when the built export's JSDoc doesn't answer the question (see "Where the
package/docs/actions.md CHANGED
@@ -268,6 +268,9 @@ changes do exactly that).
268
268
 
269
269
  ## See also
270
270
 
271
+ - [state_binding.md](./state_binding.md) — `signal`, `action` or
272
+ `command` + `commandFor`: the three shapes, and the callback that only
273
+ remembers
271
274
  - [resource.md](./resource.md) — actions created from REST callbacks, and the
272
275
  store behind them
273
276
  - [resource_with_params.md](./resource_with_params.md) — `withParams()` and
@@ -243,7 +243,10 @@ that is a navi control goes through the same state controller, and the same
243
243
 
244
244
  `SlideContainer` takes one too, though it is layout rather than a control: the
245
245
  area it shows is a piece of state like any other, and binding it is what lets
246
- something else read where the slides are — or move them by writing it.
246
+ something else read where the slides are — or move them by writing it. Bind it
247
+ to a `stateSignal` a route declares as a search param and the area is in the
248
+ address, with no route per slide — see
249
+ [navigation.md](./navigation.md#a-slidecontainer-in-the-url-a-position-that-is-not-a-place-one-came-from).
247
250
 
248
251
  Inside a `List selectable` you can bind the list, or give each `List.Item` its
249
252
  own `selected` — but not expect the two to arbitrate. An item that declares
@@ -348,6 +351,9 @@ directions; it just has nothing extra to say.
348
351
 
349
352
  ## See also
350
353
 
354
+ - [state_binding.md](./state_binding.md) — the same rule beyond controls:
355
+ a `SlideContainer`'s area, a popup being open, the position in the app — all
356
+ bound rather than copied back by a callback
351
357
  - [form_changed.md](./form_changed.md) — what a form makes of each of these:
352
358
  which fields it counts as already answered, and when it sends nothing
353
359
  - [control_object.md](./control_object.md) — several controls reading as one
@@ -41,7 +41,7 @@ This means an app can override a Navi default from its own unlayered CSS, withou
41
41
  }
42
42
  ```
43
43
 
44
- Targeting the same element is not a detail — see [`--navi-*` vs `--component-*`](#--navi-vs---component-where-the-override-has-to-go) below.
44
+ Targeting the same element is not a detail — see [`--navi-*` vs `--component-*`](#--navi--vs---component--where-the-override-has-to-go) below.
45
45
 
46
46
  ### Why actual rules stay outside any layer
47
47
 
@@ -3,6 +3,23 @@
3
3
  How to build navigation with `@jsenv/navi`: declaring routes, rendering them,
4
4
  linking to them, and turning them into tabs.
5
5
 
6
+ - [The rule that decides everything else: the position belongs in the URL](#the-rule-that-decides-everything-else-the-position-belongs-in-the-url)
7
+ - [Declaring routes](#declaring-routes)
8
+ - [A section is allowed to be a route of its own](#a-section-is-allowed-to-be-a-route-of-its-own)
9
+ - [Which values a param accepts](#which-values-a-param-accepts)
10
+ - [An address that only sends elsewhere](#an-address-that-only-sends-elsewhere)
11
+ - [Search params](#search-params)
12
+ - [Rendering routes](#rendering-routes)
13
+ - [Loading data](#loading-data)
14
+ - [Links and tab rows](#links-and-tab-rows)
15
+ - [The back arrow: `navBack`](#the-back-arrow-navback)
16
+ - [Tabs that travel: `RouteTravel`](#tabs-that-travel-routetravel)
17
+ - [Where a navigation lands: the scroll](#where-a-navigation-lands-the-scroll)
18
+ - [Creating something, then editing it](#creating-something-then-editing-it)
19
+ - [Tabs that are not routes](#tabs-that-are-not-routes)
20
+ - [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)
21
+ - [A state whose values ARE places: `history: "push"`](#a-state-whose-values-are-places-history-push)
22
+
6
23
  ## The rule that decides everything else: the position belongs in the URL
7
24
 
8
25
  Where the user is — which section, which tab, which sub-page — is state. Put it
@@ -21,8 +38,10 @@ retrofitted later:
21
38
  So the default shape of a tab row is routes: `<Nav>` + `<Link route>` +
22
39
  `<RouteTravel>`. `SlideContainer` is the exception, not the starting point — see
23
40
  [Tabs that are not routes](#tabs-that-are-not-routes) for the cases that
24
- genuinely are one, and for the middle answer: a position READ from the URL and
25
- restored on reload, without a route and without a history entry per step.
41
+ genuinely are one, and
42
+ [A `SlideContainer` in the URL](#a-slidecontainer-in-the-url-a-position-that-is-not-a-place-one-came-from)
43
+ for the middle answer: a position READ from the URL and restored on reload,
44
+ without a route and without a history entry per step.
26
45
 
27
46
  ## Declaring routes
28
47
 
@@ -289,6 +308,11 @@ Declared on the **root route**, a search param is a position that holds wherever
289
308
  one is in the application — a view mode that survives moving from page to page.
290
309
  Declared on one route, it exists only there.
291
310
 
311
+ Writing it AMENDS the history entry one is on rather than stacking a new one: a
312
+ param that qualifies a screen is not a place, and one entry per write turns a
313
+ single back-press into as many as the user moved. A state whose values ARE
314
+ places says so — see [`history: "push"`](#a-state-whose-values-are-places-history-push).
315
+
292
316
  ## Rendering routes
293
317
 
294
318
  `<Route>` is the only primitive. With `children` it is a container that renders
@@ -575,9 +599,9 @@ match at once; the first matching branch wins).
575
599
  ## Tabs that are not routes
576
600
 
577
601
  `SlideContainer` holds slides that replace one another in one box, with the same
578
- gestures and the same travelling bar, and — unless it is given a `urlParam`, see
579
- below — nothing written to the URL. Use it when the position genuinely is not a
580
- place one should be able to link to:
602
+ gestures and the same travelling bar, and — unless the signal it is bound to is
603
+ one the URL holds, see below — nothing written to the URL. Use it when the
604
+ position genuinely is not a place one should be able to link to:
581
605
 
582
606
  - the steps of a wizard, or the screens of a picker, inside a dialog or a popover
583
607
  — a popup is promoted to the browser's top layer, so no container can hold two
@@ -604,7 +628,7 @@ page. It reads which slide is on screen from the container itself, and its bar
604
628
  follows the slides, a finger dragging them included. `<Link slide>` has no href
605
629
  and behaves like a button: this is not a link to anywhere.
606
630
 
607
- ### The middle answer: a position in the URL that is not a place one came from
631
+ ### A `SlideContainer` in the URL: a position that is not a place one came from
608
632
 
609
633
  "Should a link be able to open the app on this?" has a third answer, and a wizard
610
634
  is exactly it: **yes for reading and for reloading, no for history.** The step one
@@ -615,48 +639,106 @@ and it should NOT stack an entry per step, because the back arrow of a form mean
615
639
  back-press into four, and walk the reader backwards through a form they thought
616
640
  they had left.
617
641
 
618
- Neither pure answer fits: routes would want one route per step, a real navigation
619
- per move (so a push per move), and the walk's own rules — a step held until it is
620
- answered, a confirmation reachable only by publishing — re-expressed as route
621
- guards. A plain `SlideContainer` writes nothing at all.
642
+ There is nothing to invent for it: a search param already IS a position in the
643
+ URL that replaces rather than pushes ([Search params](#search-params)). Declare
644
+ the step as one, and hand its signal to the container:
622
645
 
623
- `urlParam` is that middle answer: the container owns one search param, writes
624
- where it stands into it **by replacement**, and opens on what it names.
646
+ ```js
647
+ const stepSignal = stateSignal("when", {
648
+ id: "step",
649
+ oneOf: ["when", "where", "who", "recap", "done"],
650
+ // the step qualifies THIS visit, not the screen: a link built to the editor
651
+ // does not inherit the step one happens to be on, and it goes back to the
652
+ // default when the route stops matching
653
+ weak: true,
654
+ });
655
+ export const ALERT_EDIT_ROUTE = route("/alerts/:alertId/edit", {
656
+ searchParams: { step: stepSignal },
657
+ });
658
+ ```
625
659
 
626
660
  ```jsx
627
- <SlideContainer id="alert_editor" signal={stepSignal} urlParam="step">
661
+ <SlideContainer signal={stepSignal}>
628
662
  ```
629
663
 
630
- Two things it does that a `useEffect` calling `history.replaceState` beside the
631
- container cannot, and they are the reason it lives inside:
632
-
633
- - it writes the travels that HAPPENED. A travel a lock refused, or one the caller
634
- refused late, never reaches the address — or is written back when it does;
635
- - it READS the param through the walk rather than jumping to it. The address
636
- comes from outside the box (typed, shared, kept from a session that has moved
637
- on), so every slide between here and there is asked to let go the way a key
638
- going that way would ask it, and the first one that holds is where one stops.
639
- `?step=done` cannot open a confirmation screen for something nobody sent — and
640
- the address is then rewritten with the area actually shown, so it never says
641
- one is somewhere one is not.
664
+ That is the whole wiring, and every half of it is the piece that already
665
+ existed. Worth naming, because each answers a question a wizard actually has:
666
+
667
+ - **where it opens is the state's own default** — the first argument of
668
+ `stateSignal`. The container has a `defaultCurrent` for when it owns its
669
+ position, but a bound container does not own it: one place says where the step
670
+ starts, and it is the same place a reset goes back to;
671
+ - **the param is absent while the step IS that default** (route.js prunes it), so
672
+ `/alerts/W-123/edit` is clean on the first step, gains `?step=where` on the
673
+ second, and loses it again coming back. The address carries what differs from
674
+ the usual answer and nothing else;
675
+ - **the container walks to the step rather than jumping to it.** The address comes
676
+ from outside the box (typed, shared, kept from a session that has moved on), so
677
+ every slide between here and there is asked to let go the way a key going that
678
+ way would ask it, and the first one that holds is where one stops. `?step=done`
679
+ cannot open a confirmation screen for something nobody sent. The signal is then
680
+ written with the area actually shown, so the address never says one is
681
+ somewhere one is not;
682
+ - **only the travels that HAPPENED are written.** A travel a lock refused, or one
683
+ `onCurrentChange` refused late, never reaches the signal — or is written back
684
+ when it does.
642
685
 
643
686
  A container remembers nothing across a reload, so a step whose `required` the app
644
687
  knows is already satisfied says so itself (`required={!alreadyFilled}`); the same
645
688
  holds for a hold that a finished job lifts (`preventNavNext={!published}`).
646
689
 
647
- `history: "push"` is the other half, for slides that ARE places one came from — a
648
- gallery one browses:
690
+ **A start that depends on where one is** — a wizard creating something opens on
691
+ its first question, the same wizard editing something opens on its summary — is
692
+ not a prop on the container either: a `stateSignal` takes a SIGNAL as its default
693
+ and follows it as long as nobody has answered otherwise.
649
694
 
650
- ```jsx
651
- <SlideContainer urlParam={{ name: "photo", history: "push" }}>
695
+ ```js
696
+ const stepDefaultSignal = computed(() =>
697
+ // `matchingSignal`, not `matching`: the plain property is a mirror, the
698
+ // signal is what a computed can follow
699
+ ALERT_EDIT_ROUTE.matchingSignal.value ? "recap" : "when",
700
+ );
701
+ const stepSignal = stateSignal(stepDefaultSignal, { id: "step", weak: true });
652
702
  ```
653
703
 
654
- Even there, a slide reached by DRAGGING replaces rather than pushes: swiping back
655
- and forth with a thumb is browsing, not a trail one wants to walk home along.
704
+ The pruning follows it too, so both addresses stay clean on the step they open
705
+ on — `/alerts/create` on "when", `/alerts/W-123/edit` on "recap" — and a step
706
+ someone actually chose survives the default moving under it.
656
707
 
657
- What `urlParam` is not: a route. Nothing is declared, nothing matches, no page
658
- transition plays — what travels is the box, and the address is a label on where
659
- the box stands. A position several parts of the app must react to is still a
660
- route.
708
+ Two containers on one screen are two signals, and that is the whole answer to
709
+ "which one owns the param": the one holding the route's signal. A gallery of
710
+ seven wizards side by side hands each of them a `useSignal` of its own, and
711
+ nothing goes into the address.
712
+
713
+ ### A state whose values ARE places: `history: "push"`
714
+
715
+ Replacement is the default because most URL-held state qualifies the screen one
716
+ is on. A state whose values are places one came from — the photo being looked at
717
+ in a gallery — says so where the state is declared:
718
+
719
+ ```js
720
+ const photoSignal = stateSignal(undefined, { id: "photo", history: "push" });
721
+ ```
722
+
723
+ Every write of it then stacks an entry, wherever the write comes from. Except
724
+ where the writer knows this particular move is not one: a slide reached by
725
+ DRAGGING replaces even in a container that pushes, because swiping back and forth
726
+ with a thumb is browsing, not a trail one wants to walk home along. That is said
727
+ at the write rather than declared:
728
+
729
+ ```js
730
+ photoSignal.set(nextPhoto, { history: "replace" });
731
+ ```
732
+
733
+ `SlideContainer` already does exactly that for its own drags, so a gallery gets
734
+ it by declaring the state and nothing else.
735
+
736
+ ### What this is not
737
+
738
+ It is not a route. Nothing is declared for the steps themselves, nothing matches
739
+ on them, and no page transition plays — a transition needs the page to change
740
+ ([route_transitions.md](./route_transitions.md)), and a search param moving is
741
+ not one. What travels is the box, and the address is a label on where the box
742
+ stands. A position several parts of the app must react to is still a route.
661
743
 
662
744
  Demo: [../src/layout/demos/8_slide_container_demo.html](../src/layout/demos/8_slide_container_demo.html).
@@ -97,11 +97,24 @@ This is the same entry point the attributes go through: same target resolution,
97
97
  same command proxies, same events. The popup stays uncontrolled, and keeps its
98
98
  say over closing.
99
99
 
100
- `event` is the DOM event the decision came from. Pass it whenever there is one:
101
- it is chained into the request event, and that chain is what lets the popup
102
- handle focus correctly (which element to give focus back to, whether a
103
- mousedown's click must be swallowed). Omit it only when nothing user-initiated
104
- triggered the command.
100
+ `event` is what caused the decision, and it is mandatory — triggering a command
101
+ without one throws. It is chained into the request event, and that chain is what
102
+ lets the popup handle focus correctly (which element to give focus back to,
103
+ whether a mousedown's click must be swallowed) and what the debug panel groups
104
+ the whole sequence under.
105
+
106
+ Almost always there is a DOM event to hand over, because almost always something
107
+ was done to the page. When there genuinely was not — a timer firing, an action
108
+ settling, a signal changing — say so with a `CustomEvent` named after what
109
+ happened rather than leaving the origin unsaid:
110
+
111
+ ```js
112
+ import { chainEvent, triggerNaviCommand } from "@jsenv/navi";
113
+
114
+ const expiredEvent = new CustomEvent("session_expired");
115
+ chainEvent(expiredEvent, causeEvent); // when something did precede it
116
+ triggerNaviCommand(dialogRef.current, "--navi-open", expiredEvent);
117
+ ```
105
118
 
106
119
  ## Which element receives the command
107
120
 
@@ -0,0 +1,142 @@
1
+ # Bound, not wired
2
+
3
+ Every piece of state navi shows already has a way in and a way out: the value in
4
+ a control, the area a `SlideContainer` stands on, whether a popup is open, where
5
+ the user is in the application. A callback whose whole body copies that state
6
+ into a variable of your own is a second road beside one that exists — and from
7
+ then on there are two owners for one truth.
8
+
9
+ - [The three shapes](#the-three-shapes)
10
+ - [The callback that only remembers](#the-callback-that-only-remembers)
11
+ - [Why it is not merely shorter](#why-it-is-not-merely-shorter)
12
+ - [What is left for the callback](#what-is-left-for-the-callback)
13
+ - [What binds itself](#what-binds-itself)
14
+ - [When there is no binding](#when-there-is-no-binding)
15
+
16
+ ## The three shapes
17
+
18
+ Almost everything an application has to say about a component is one of three
19
+ things, and each has its own prop:
20
+
21
+ | what the code has to say | how it is said |
22
+ | --------------------------------------------- | ------------------------------------------ |
23
+ | this state is the app's, both ways | `signal` |
24
+ | something HAPPENS when it changes | `action` (`uiAction` for what cannot fail) |
25
+ | a press asks something of a component near it | `command` + `commandFor` |
26
+
27
+ Reaching for a handler that is none of the three — an `onClick`, an
28
+ `onCurrentChange`, a `uiAction` writing a signal — is the sign that one of them
29
+ was missed.
30
+
31
+ ## The callback that only remembers
32
+
33
+ The same mistake, in four places:
34
+
35
+ ```jsx
36
+ // ✗ the app is told where the slides went, and writes it back down
37
+ <SlideContainer
38
+ current={step}
39
+ onCurrentChange={(area) => {
40
+ stepSignal.value = area;
41
+ }}
42
+ />
43
+ // ✓
44
+ <SlideContainer signal={stepSignal} />
45
+
46
+ // ✗ the control is told what it now holds, and the app writes it back down
47
+ <Picker value={side} uiAction={(value) => (sideSignal.value = value)} />
48
+ // ✓
49
+ <Picker signal={sideSignal} />
50
+
51
+ // ✗ a button proposing a value to a control, straight into the signal
52
+ <Button onClick={() => (durationSignal.value = ninetyMinutes)}>1h30</Button>
53
+ // ✓
54
+ <Button command="--navi-update" commandFor="duration" value={ninetyMinutes}>
55
+ 1h30
56
+ </Button>
57
+
58
+ // ✗ the popup is told it opened, and the app writes it back down
59
+ <Dialog
60
+ open={isOpen}
61
+ onOpen={() => (openSignal.value = true)}
62
+ onClose={() => (openSignal.value = false)}
63
+ />
64
+ // ✓
65
+ <Dialog signal={openSignal} />
66
+ ```
67
+
68
+ In each pair the second line is not a tidier spelling of the first: it is the
69
+ binding the component already implements, and the first is a partial
70
+ re-implementation of it.
71
+
72
+ ## Why it is not merely shorter
73
+
74
+ A binding is written by **everything that moves the state**, not only by the
75
+ gesture the caller thought of. A `SlideContainer`'s signal is written by a drag,
76
+ by an arrow key, by a `--navi-right` command sent from a chevron outside the
77
+ box, by the address when the signal is a route's own, by a step refusing to be
78
+ left. A control's
79
+ signal is written by the user typing, by a group placing its children, by a
80
+ picker committing on close, by a `--navi-update` from a shortcut button. A
81
+ hand-written callback catches the cases it was written for and silently misses
82
+ the rest — and the drift only shows on the screen where a second gesture was
83
+ added.
84
+
85
+ It reads in the other direction too, which no callback gives at all: writing the
86
+ signal MOVES the component. The wheels roll, the slides travel, the popup opens.
87
+ That is what replaces the `key` or the `value` prop pushed down to force a
88
+ component into a state.
89
+
90
+ And it goes through navi's interaction gate. A `command` on a read-only,
91
+ disabled or busy control is refused and says why; an `onClick` doing the same
92
+ write is plain DOM, fires on a greyed-out button and rewrites a value nobody was
93
+ allowed to change.
94
+
95
+ ## What is left for the callback
96
+
97
+ Binding the state does not remove the callback — it removes one job from it:
98
+
99
+ - **`onCurrentChange` with a `signal` bound still fires**, and is still worth
100
+ writing: `cause` says whether the slide was pressed, dragged, or asked for by
101
+ the address — which is what tells a history push from a replace — and
102
+ returning `false` REFUSES the travel. Neither of those is state.
103
+ - **`uiAction`** is for what moves with the value without being it: logging,
104
+ something else on screen following along. Not "remember it".
105
+ - **`action`** is for what can fail or take time — it carries the busy state and
106
+ the error, which a `uiAction` does not (see
107
+ [actions.md](./actions.md#action-or-uiaction)).
108
+ - **`onClick`** is for imperative work with nothing to send, nothing to open and
109
+ nothing to propose.
110
+
111
+ ## What binds itself
112
+
113
+ | the state | how it binds | where it is written up |
114
+ | --------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
115
+ | a control's value | `signal` (every control, every group) | [control_value.md](./control_value.md) |
116
+ | the area a `SlideContainer` is on | `signal` (a route's search param signal puts it in the URL) | its JSDoc, [navigation.md](./navigation.md#a-slidecontainer-in-the-url-a-position-that-is-not-a-place-one-came-from) |
117
+ | a popup being open | `signal`, `open`, `navState` | [popup_open.md](./popup_open.md) |
118
+ | where the user is | route + search-param signals | [navigation.md](./navigation.md) |
119
+ | a value proposed to a control | `command="--navi-update"` | [control_value.md](./control_value.md#a-button-that-proposes-a-value-is---navi-update) |
120
+ | the state of an async run | an `action`, read by `useAsyncData` | [actions.md](./actions.md) |
121
+
122
+ ## When there is no binding
123
+
124
+ Sometimes the state being wired by hand has no prop for it. That is not a
125
+ licence to wire it — it is a finding, and it is one of two:
126
+
127
+ - the state is already held somewhere and the app is keeping a parallel copy of
128
+ it (the URL, the control itself, an action's own state) — the fix is to read
129
+ it where it lives;
130
+ - or navi is genuinely missing a binding, and should gain one.
131
+
132
+ Say which, rather than leaving a hand-wired callback behind: it is the version
133
+ the next screen copies, and by then it is the app's pattern.
134
+
135
+ ## See also
136
+
137
+ - [control_value.md](./control_value.md) — who holds a control's value, and what
138
+ a bound signal holds control by control
139
+ - [actions.md](./actions.md#action-or-uiaction) — `action` or `uiAction`
140
+ - [popup_open.md](./popup_open.md) — who owns the fact that a popup is open
141
+ - [interactions.md](./interactions.md) — a gesture is named, not read by hand:
142
+ the same rule, one layer down
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/navi",
3
- "version": "0.29.141",
3
+ "version": "0.29.143",
4
4
  "type": "module",
5
5
  "description": "Library of components including navigation to create frontend applications",
6
6
  "repository": {