@jsenv/navi 0.29.165 → 0.29.166
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.
- package/dist/jsenv_navi.js +574 -94
- package/dist/jsenv_navi.js.map +12 -8
- package/docs/AI_INSTRUCTIONS.md +8 -3
- package/docs/navigation.md +167 -0
- package/docs/popup_open.md +4 -1
- package/docs/route_transitions.md +8 -0
- package/package.json +1 -1
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -331,9 +331,14 @@ consistency across the app, not from any single call site.
|
|
|
331
331
|
restored on reload without a route and without an entry per step: a search
|
|
332
332
|
param signal handed to a `SlideContainer`, walked to rather than jumped to,
|
|
333
333
|
and `history: "push"` for the states whose values ARE places (with the write
|
|
334
|
-
that says otherwise, `signal.set(v, { history })`).
|
|
335
|
-
|
|
336
|
-
|
|
334
|
+
that says otherwise, `signal.set(v, { history })`). It also holds the rule for
|
|
335
|
+
an OVERLAY — something opened from every screen and drawn over whatever the
|
|
336
|
+
reader was on: the URL may say what is drawn over the screen, it must never
|
|
337
|
+
name a place the reader is not at, which is why `/me/settings` is wrong for it
|
|
338
|
+
and `/places?settings` is right (the layer's address contains the address of
|
|
339
|
+
what it covers, so closing is exact even from a shared link). Read it before
|
|
340
|
+
writing any routing code — the position of the user belongs in the URL by
|
|
341
|
+
default, and that decision is not retrofittable.
|
|
337
342
|
- Source code and demos on GitHub:
|
|
338
343
|
https://github.com/jsenv/core/tree/main/packages/frontend/navi — where to go
|
|
339
344
|
when the built export's JSDoc doesn't answer the question (see "Where the
|
package/docs/navigation.md
CHANGED
|
@@ -19,6 +19,14 @@ linking to them, and turning them into tabs.
|
|
|
19
19
|
- [Tabs that are not routes](#tabs-that-are-not-routes)
|
|
20
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
21
|
- [A state whose values ARE places: `history: "push"`](#a-state-whose-values-are-places-history-push)
|
|
22
|
+
- [A layer over the screen: what its address may say](#a-layer-over-the-screen-what-its-address-may-say)
|
|
23
|
+
- [Why a layer is a routing question at all](#why-a-layer-is-a-routing-question-at-all)
|
|
24
|
+
- [`/me/settings` names a place the reader is not at](#mesettings-names-a-place-the-reader-is-not-at)
|
|
25
|
+
- [It is not "use a search param"](#it-is-not-use-a-search-param)
|
|
26
|
+
- [What the search param buys: the way back is in the address](#what-the-search-param-buys-the-way-back-is-in-the-address)
|
|
27
|
+
- [The wiring](#the-wiring)
|
|
28
|
+
- [Places inside the layer](#places-inside-the-layer)
|
|
29
|
+
- [What it costs](#what-it-costs)
|
|
22
30
|
|
|
23
31
|
## The rule that decides everything else: the position belongs in the URL
|
|
24
32
|
|
|
@@ -747,3 +755,162 @@ not one. What travels is the box, and the address is a label on where the box
|
|
|
747
755
|
stands. A position several parts of the app must react to is still a route.
|
|
748
756
|
|
|
749
757
|
Demo: [../src/layout/demos/8_slide_container_demo.html](../src/layout/demos/8_slide_container_demo.html).
|
|
758
|
+
|
|
759
|
+
## A layer over the screen: what its address may say
|
|
760
|
+
|
|
761
|
+
Some things are opened from everywhere and drawn OVER whatever the reader was
|
|
762
|
+
looking at: the settings behind a gear in the top bar, a photo, a help sheet.
|
|
763
|
+
Closing one puts the reader back exactly where they were — not on a page the app
|
|
764
|
+
picked, on the one they had. And the address has to hold it, for the same reasons
|
|
765
|
+
every other position does: a reload lands on it, a link opens on it, the back
|
|
766
|
+
button closes it.
|
|
767
|
+
|
|
768
|
+
### Why a layer is a routing question at all
|
|
769
|
+
|
|
770
|
+
Layout should not decide routing. But routing decides a great deal of what is on
|
|
771
|
+
screen: which page is mounted, whose data loads, which bar entry lights up, which
|
|
772
|
+
movement plays between two screens. An address is not a label — it is read by
|
|
773
|
+
everything — so a layer, which is a pure fact of layout, cannot be given just any
|
|
774
|
+
address.
|
|
775
|
+
|
|
776
|
+
The rule that falls out of it, and the whole of this section:
|
|
777
|
+
|
|
778
|
+
> The URL may say what is drawn OVER the screen. It must never name a place the
|
|
779
|
+
> reader is not at.
|
|
780
|
+
|
|
781
|
+
### `/me/settings` names a place the reader is not at
|
|
782
|
+
|
|
783
|
+
Settings opened from anywhere and drawn over anything are not inside `/me`, even
|
|
784
|
+
when they conceptually belong to "me". Written `/me/settings`, the router
|
|
785
|
+
believes otherwise, and everything that reads the router inherits the belief:
|
|
786
|
+
|
|
787
|
+
- the bar entry for the "me" section lights up while the settings cover a game;
|
|
788
|
+
- `/me` matches, so the screen the reader was on is REPLACED. The covering is a
|
|
789
|
+
fiction that lasts as long as the animation, and the page beneath is gone;
|
|
790
|
+
- a route transition plays a crossing between two pages that never crossed, and
|
|
791
|
+
its back half plays on every way out — the sheet lifts to reveal a page that
|
|
792
|
+
was never underneath;
|
|
793
|
+
- and it is wrong on the way out too: closing does not return to `/me`, it
|
|
794
|
+
returns to wherever the reader was, which the address never said.
|
|
795
|
+
|
|
796
|
+
These do not get fixed one at a time. `currentExcept` stops one link from
|
|
797
|
+
claiming "you are here"; a `"none"` relation silences one crossing; a hand-rolled
|
|
798
|
+
`active` replaces one reading. Each patches a reader of the lie without removing
|
|
799
|
+
it, and the next reader arrives quietly wrong.
|
|
800
|
+
|
|
801
|
+
### It is not "use a search param"
|
|
802
|
+
|
|
803
|
+
`/settings` at the top level does not claim to be inside `/me` — it is honest
|
|
804
|
+
about not being there, and every symptom above goes with it. What it still is,
|
|
805
|
+
though, is a PAGE: it names a screen, so the screen beneath is gone, and closing
|
|
806
|
+
has nowhere written to return to. `whatever/settings` is the same answer in a
|
|
807
|
+
longer word.
|
|
808
|
+
|
|
809
|
+
So the rule is not about search params. It is about not claiming a position. A
|
|
810
|
+
path can be honest; what a path cannot do is carry what is underneath.
|
|
811
|
+
|
|
812
|
+
### What the search param buys: the way back is in the address
|
|
813
|
+
|
|
814
|
+
`/places?settings` says two things at once — which screen the reader is on, and
|
|
815
|
+
what is drawn over it. That is what makes closing exact from a cold start: a
|
|
816
|
+
reload, a link someone sent, a new tab. The layer's address CONTAINS the address
|
|
817
|
+
of what it covers, so "close" needs no memory at all.
|
|
818
|
+
|
|
819
|
+
`/settings` has to remember instead. In practice the browser often does — after a
|
|
820
|
+
reload the session's history is still there, so a back-press lands somewhere —
|
|
821
|
+
but that is the BROWSER's memory of this tab, not the address. A link someone
|
|
822
|
+
else opens has none of it, and neither does a bookmark. Only the address travels,
|
|
823
|
+
which is what makes the param shape the right model rather than the convenient
|
|
824
|
+
one.
|
|
825
|
+
|
|
826
|
+
### The wiring
|
|
827
|
+
|
|
828
|
+
```js
|
|
829
|
+
const settingsSignal = stateSignal(false, {
|
|
830
|
+
id: "settings",
|
|
831
|
+
type: "boolean",
|
|
832
|
+
weak: true,
|
|
833
|
+
history: "push",
|
|
834
|
+
});
|
|
835
|
+
export const ROOT_ROUTE = route("/", {
|
|
836
|
+
searchParams: { settings: settingsSignal },
|
|
837
|
+
});
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
```jsx
|
|
841
|
+
<Button command="--navi-toggle" commandFor="settings_panel">⚙</Button>
|
|
842
|
+
…
|
|
843
|
+
<SidePanel id="settings_panel" signal={settingsSignal} side="top" expandY />
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
Every piece of it answers something:
|
|
847
|
+
|
|
848
|
+
- **declared on the ROOT route**, so the layer opens over every screen. Declared
|
|
849
|
+
on one route it would only exist there, and a door in the furniture is on every
|
|
850
|
+
screen ([Search params](#search-params));
|
|
851
|
+
- **`weak`**, so a link built to a page never inherits a layer that happens to be
|
|
852
|
+
open. A layer qualifies one visit; it is not part of anyone's address;
|
|
853
|
+
- **`history: "push"`**, so the layer is a place one came from and the back button
|
|
854
|
+
closes it. The closing itself is never an entry of its own — see
|
|
855
|
+
[popup_open.md](./popup_open.md), which owns what a `signal` bound to a URL
|
|
856
|
+
writes on open, on close, and on cancel;
|
|
857
|
+
- **the panel is rendered outside the page area**, next to the router rather than
|
|
858
|
+
inside it. It is drawn over the pages and the fixed bars alike, and it stays
|
|
859
|
+
mounted while closed so it has somewhere to animate from;
|
|
860
|
+
- **`expandY`**, because a sheet is content-tall by default and a layer covers
|
|
861
|
+
the screen. It is also what any tab row inside it must NOT be given: `expand`
|
|
862
|
+
is both axes, and in the panel's own column that makes the row eat the height
|
|
863
|
+
and push everything below it off screen;
|
|
864
|
+
- **no `defineRouteTransition` for it.** Nothing crosses: the page beneath does
|
|
865
|
+
not change, so there is no pair of pages and no movement between them. A layer
|
|
866
|
+
has an entrance of its own, and route transitions are for pages replacing pages
|
|
867
|
+
([route_transitions.md](./route_transitions.md)).
|
|
868
|
+
|
|
869
|
+
### Places inside the layer
|
|
870
|
+
|
|
871
|
+
A layer big enough to have tabs holds them the way a wizard does — a search param
|
|
872
|
+
of its own, `oneOf` its pages, replacing rather than pushing, so the back button
|
|
873
|
+
closes the layer instead of stepping back one tab. Everything in
|
|
874
|
+
[A `SlideContainer` in the URL](#a-slidecontainer-in-the-url-a-position-that-is-not-a-place-one-came-from)
|
|
875
|
+
applies unchanged, and a layer is exactly the case that section names for
|
|
876
|
+
`SlideContainer` over `RouteTravel`: a popup lives in the browser's top layer,
|
|
877
|
+
where two pages cannot stand side by side.
|
|
878
|
+
|
|
879
|
+
```js
|
|
880
|
+
const settingsTabSignal = stateSignal("account", {
|
|
881
|
+
id: "settings_tab",
|
|
882
|
+
oneOf: ["account", "alerts", "advanced"],
|
|
883
|
+
weak: true,
|
|
884
|
+
});
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
The address then grows and shrinks with what the reader does: `/me` closed,
|
|
888
|
+
`/me?settings` open on the tab it opens on, `/me?settings&settings_tab=alerts`
|
|
889
|
+
one tab further, and back to `/me?settings` returning to the first.
|
|
890
|
+
|
|
891
|
+
Two params, because they answer two questions — is the layer there, and which of
|
|
892
|
+
its pages is shown. One param whose value is "which page of the layer, or none"
|
|
893
|
+
(`?settings=alerts`) is arguably the shape the address wants, and it is not
|
|
894
|
+
expressible today: a popup says its open state as a boolean, so it would
|
|
895
|
+
overwrite the tab on every open.
|
|
896
|
+
|
|
897
|
+
### What it costs
|
|
898
|
+
|
|
899
|
+
This is a compromise, and it is worth stating rather than discovering:
|
|
900
|
+
|
|
901
|
+
- **the page beneath stays mounted and alive under an opaque layer.** Its actions
|
|
902
|
+
keep running, refreshing, retrying, for something nobody can see. Nothing tells
|
|
903
|
+
a route "you are covered", and nothing should be inferred from a layer being
|
|
904
|
+
open — the reader is still on that page, and will be back on it in a moment;
|
|
905
|
+
- **the router knows nothing about layers.** `<Route>` renders pages; the layer is
|
|
906
|
+
drawn beside them by the application. The address is honest about it, but "over"
|
|
907
|
+
is not a routing concept, and no `<Route>` shape expresses it;
|
|
908
|
+
- **the app really is on the page underneath**, which is right for `aria-current`,
|
|
909
|
+
for the bar, for a link built while the layer is open — and is exactly wrong for
|
|
910
|
+
anything wanting "the settings are what is current". If something needs to
|
|
911
|
+
answer that, it reads the layer's own state, not the router.
|
|
912
|
+
|
|
913
|
+
Demo:
|
|
914
|
+
[../src/nav/demos/route_transition_fixed_bars/route_transition_fixed_bars.html](../src/nav/demos/route_transition_fixed_bars/route_transition_fixed_bars.html)
|
|
915
|
+
— a bar holding both doors side by side: notifications, a page that replaces the
|
|
916
|
+
screen with a route transition, and settings, a layer that covers it.
|
package/docs/popup_open.md
CHANGED
|
@@ -533,7 +533,10 @@ is nav state by construction.
|
|
|
533
533
|
|
|
534
534
|
The two meet when the signal IS a route's: a search-param `stateSignal` given to
|
|
535
535
|
`signal` puts the open state in the address itself, where a link can point at
|
|
536
|
-
it.
|
|
536
|
+
it. That is also the shape a popup takes when it is a LAYER over the screen —
|
|
537
|
+
settings opened from every screen and closed back onto the one the reader was on
|
|
538
|
+
— and what the address may and may not claim there is a decision of its own:
|
|
539
|
+
[navigation.md](./navigation.md#a-layer-over-the-screen-what-its-address-may-say).
|
|
537
540
|
|
|
538
541
|
### `open`, and what it costs
|
|
539
542
|
|
|
@@ -65,6 +65,14 @@ happens to be everywhere. Write it for a page whose door really is furniture; a
|
|
|
65
65
|
page reached from a screen has that screen to be paired with, and the pair says
|
|
66
66
|
more.
|
|
67
67
|
|
|
68
|
+
**Such a page is still a PAGE**, and that is the question to settle before
|
|
69
|
+
writing the relation: it takes the screen's place, and the reader comes back to
|
|
70
|
+
whatever the router puts there — not necessarily to what they were on. A door in
|
|
71
|
+
the furniture whose destination must be drawn OVER the screen, and must give it
|
|
72
|
+
back exactly on closing, is not a page at all: it is a layer, its address says so
|
|
73
|
+
differently, and no relation is written for it. See
|
|
74
|
+
[navigation.md](./navigation.md#a-layer-over-the-screen-what-its-address-may-say).
|
|
75
|
+
|
|
68
76
|
## Choosing a movement
|
|
69
77
|
|
|
70
78
|
- **`slide-x`** — going INTO something: a list item opened, a card followed, a
|