@jsenv/navi 0.29.478 → 0.29.479
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/dev/jsenv_navi.js +1729 -680
- package/dist/dev/jsenv_navi.js.map +38 -36
- package/dist/jsenv_navi.js +2158 -550
- package/dist/jsenv_navi.js.map +38 -36
- package/docs/AI_INSTRUCTIONS.md +3 -1
- package/docs/actions.md +23 -0
- package/docs/drag_to_travel.md +9 -3
- package/docs/form_changed.md +4 -3
- package/docs/interactions.md +8 -4
- package/docs/navigation.md +31 -2
- package/docs/pan_zoom.md +6 -4
- package/docs/popup_backdrop.md +5 -0
- package/docs/popup_lift.md +4 -3
- package/docs/route_transitions.md +7 -3
- package/docs/scroll.md +5 -5
- package/docs/testid.md +8 -5
- package/docs/view_transitions.md +3 -2
- package/package.json +3 -3
package/docs/AI_INSTRUCTIONS.md
CHANGED
|
@@ -26,7 +26,9 @@ source of knowledge has one job:
|
|
|
26
26
|
- **The built code, `dist/jsenv_navi.js`** — the exhaustive truth about the API,
|
|
27
27
|
JSDoc included, always under `node_modules/@jsenv/navi/`. When a signature, a
|
|
28
28
|
default or an accepted value is what you need, read it there rather than
|
|
29
|
-
guessing.
|
|
29
|
+
guessing. The formatters navi re-exports from `@jsenv/humanize` (`format*`,
|
|
30
|
+
`createI18n`, `interpolateText`) are a re-export there: their JSDoc is in
|
|
31
|
+
`node_modules/@jsenv/humanize/dist/browser/jsenv_humanize_browser.js`.
|
|
30
32
|
- **Sources, demos and tests** — how an export is really used:
|
|
31
33
|
`src/**/demos/*_demo.html` exercise one component per page, prop by prop. They
|
|
32
34
|
are not published to npm, so they live only in the repo
|
package/docs/actions.md
CHANGED
|
@@ -22,6 +22,7 @@ them by hand — see [resource.md](./resource.md).
|
|
|
22
22
|
|
|
23
23
|
- [Params: `bindParams`, and calling the action](#params-bindparams-and-calling-the-action)
|
|
24
24
|
- [The answer is kept on the instance that ran](#the-answer-is-kept-on-the-instance-that-ran)
|
|
25
|
+
- [Nothing to ask, and coming back](#nothing-to-ask-and-coming-back)
|
|
25
26
|
- [The in-between states of a gesture are real params](#the-in-between-states-of-a-gesture-are-real-params)
|
|
26
27
|
- [Running: `run`, `rerun`, `prerun`, `reset`](#running-run-rerun-prerun-reset)
|
|
27
28
|
- [Aborting saves resources, it does not undo](#aborting-saves-resources-it-does-not-undo)
|
|
@@ -124,6 +125,28 @@ verb — `computed(() => ME.GET.dataSignal.value)` — only works for a verb
|
|
|
124
125
|
something runs **directly**, with no params; anything else derives from the
|
|
125
126
|
instance: `computed(() => COUNTS.dataSignal.value)`.
|
|
126
127
|
|
|
128
|
+
### Nothing to ask, and coming back
|
|
129
|
+
|
|
130
|
+
A binding has nothing to ask while its params as a whole are `undefined` or
|
|
131
|
+
`null` — a question computed from a form still missing a field.
|
|
132
|
+
`{ id: undefined }` is not that: it is a question, and it is asked. With nothing
|
|
133
|
+
to ask, the binding reads an instance holding nothing, idle: no data, no wait.
|
|
134
|
+
The previous answer stands in only while a newer one is coming, and none is.
|
|
135
|
+
|
|
136
|
+
What the previous params got stays on the instance that ran. A run still out
|
|
137
|
+
for them is called off by `actionRunEffect` (so by a route action) and carries
|
|
138
|
+
on under a plain binding; either way nothing is reset — an action does not
|
|
139
|
+
start over because its params went away.
|
|
140
|
+
|
|
141
|
+
Whether coming back to those params finds that answer depends on the instance
|
|
142
|
+
still being there. Without `keep` it lives as long as something references it,
|
|
143
|
+
and once nothing does it goes at a moment nothing in the code decides: coming
|
|
144
|
+
back may find it or ask again, and neither is something to build on.
|
|
145
|
+
`createAction(callback, { keep: { ms } })` is what decides. The answer stays
|
|
146
|
+
good for `ms` from the moment it landed, and the first screen asking for those
|
|
147
|
+
params after that asks again: a read worth asking at each visit — a
|
|
148
|
+
measurement, a quota — declares a short one.
|
|
149
|
+
|
|
127
150
|
### The in-between states of a gesture are real params
|
|
128
151
|
|
|
129
152
|
The delay decides _when_ a question goes out, never _which_ one is coherent: it
|
package/docs/drag_to_travel.md
CHANGED
|
@@ -79,9 +79,15 @@ are read at the press, before the box moves:
|
|
|
79
79
|
[interactions.md](./interactions.md#an-affordance-inside-somebody-elses-box-selfinteractions));
|
|
80
80
|
and a popover or a `<dialog>` inside the box, which is a layer over it. A
|
|
81
81
|
button, a link or a checkbox is not among them on purpose: a drag from one
|
|
82
|
-
travels, and the click it would have made is swallowed
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
travels, and the click it would have made is swallowed before any listener
|
|
83
|
+
hears it, the ancestors' included: an `onClick` on something that travels
|
|
84
|
+
needs no distance check of its own. For the same reason
|
|
85
|
+
navi refuses the browser's own drag of a link or an image for that press: it
|
|
86
|
+
starts a few pixels in and would take the pointer before the travel forms.
|
|
87
|
+
An app that wants an image dragged out of the page marks it
|
|
88
|
+
`data-no-drag-travel`. A field that only reads the press — a picker's façade
|
|
89
|
+
is an `<input>` that opens a popup — says so with `data-press-only`, and a
|
|
90
|
+
travel starts there like anywhere else.
|
|
85
91
|
2. **A scroller in between with room left that way.** It keeps the gesture until
|
|
86
92
|
it has no room left, and only then hands the travel over — so a row that
|
|
87
93
|
scrolls sideways inside a page still scrolls sideways. A row swiped with
|
package/docs/form_changed.md
CHANGED
|
@@ -53,9 +53,10 @@ for.
|
|
|
53
53
|
|
|
54
54
|
### A popup that stays open
|
|
55
55
|
|
|
56
|
-
A popup closes once a form inside it is sent
|
|
57
|
-
|
|
58
|
-
linked
|
|
56
|
+
A popup closes once a form inside it is sent, even when the send took the form
|
|
57
|
+
out of the page. When it is a place one keeps working in — a panel where a push
|
|
58
|
+
is sent, two fields are fixed, an account is linked, an answer drawn where the
|
|
59
|
+
form was ("link sent, check your inbox") — the form says it stays:
|
|
59
60
|
|
|
60
61
|
```jsx
|
|
61
62
|
<SidePanel signal={openUserSignal} value={user.id}>
|
package/docs/interactions.md
CHANGED
|
@@ -478,10 +478,14 @@ teardown, so a travelling container above it does not take the gesture (see
|
|
|
478
478
|
selection refused only for the length of its gesture — in time for a mouse,
|
|
479
479
|
too late for a finger held still — so it says `user-select: none` itself (see
|
|
480
480
|
[drag_interactions.md](./drag_interactions.md#the-text-inside-user-select-none)).
|
|
481
|
-
- **
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
481
|
+
- **What a press moves cannot also be dragged out of the page.** A swipe, a
|
|
482
|
+
travel (a `SlideContainer`, a `RouteTravel`, a sheet pushed back) and a `pan`
|
|
483
|
+
surface refuse the browser's own drag of the link or image a press lands on —
|
|
484
|
+
a native drag _is_ press-and-move, a link or an image is draggable without
|
|
485
|
+
anyone asking, and the browser starts it a few pixels in, before any of them
|
|
486
|
+
has decided. One gesture cannot mean both. What they leave alone keeps its
|
|
487
|
+
drag: an element inside marked `data-no-drag-travel` reads the pointer itself.
|
|
488
|
+
An element declaring a swipe also gets `draggable={false}`.
|
|
485
489
|
- **A popup can open while the finger is still down** — a menu under a waiting
|
|
486
490
|
finger is the native gesture (see
|
|
487
491
|
[popup_open.md](./popup_open.md#opening-while-the-finger-is-still-down), and
|
package/docs/navigation.md
CHANGED
|
@@ -125,6 +125,22 @@ already open — a tab pointing at the current page cannot be clicked. The defau
|
|
|
125
125
|
section has no segment, so its literal route is the **parent** of the
|
|
126
126
|
parameterized one, and still does not inherit the param.
|
|
127
127
|
|
|
128
|
+
The default section can also have an address of its own, when the bare address
|
|
129
|
+
is another page:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
export const PROFILE_ROUTE = route("/me");
|
|
133
|
+
export const ADDRESS_BOOK_ROUTE = route(`/me/:section=${bookSectionSignal}`); // default "players"
|
|
134
|
+
export const PLAYERS_ROUTE = route("/me/players");
|
|
135
|
+
export const GROUPS_ROUTE = route("/me/groups");
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Declaring both `/me` and a literal for the default is what says so: the param
|
|
139
|
+
is then required, `/me` is the profile alone, and `ADDRESS_BOOK_ROUTE.buildUrl()`
|
|
140
|
+
gives `/me/players` or `/me/groups`, never `/me`. A visit to the profile leaves
|
|
141
|
+
the section where it was. Leave out the literal for the default and `/me` is the
|
|
142
|
+
default section, as with `/games/me` above.
|
|
143
|
+
|
|
128
144
|
That the literals exist is what tells navi these values name pages. Where none is
|
|
129
145
|
declared, the value stays a qualifier, carried by an ancestor url:
|
|
130
146
|
|
|
@@ -157,8 +173,10 @@ export const GAME_ROUTE = route(`/:gameId=${gameIdSignal}`, {
|
|
|
157
173
|
This is what lets a param sit at the root without swallowing every single-segment
|
|
158
174
|
address: `/cgu` stays another route's url, `<Route fallback>` is reachable, and
|
|
159
175
|
no signal is written for a url this route has nothing to do with. A constrained
|
|
160
|
-
param is also **required**: `/:gameId` does not match
|
|
161
|
-
|
|
176
|
+
param is also **required**: `/:gameId` does not match `/` — unless its default
|
|
177
|
+
stands for the bare address, as `/games/me` does for `/games/me/:section` (see
|
|
178
|
+
[Declaring the sections](#declaring-the-sections-is-what-makes-them-places)).
|
|
179
|
+
The accepted forms are in `route()`'s JSDoc.
|
|
162
180
|
|
|
163
181
|
#### Constrain the shape, never the existence
|
|
164
182
|
|
|
@@ -258,6 +276,9 @@ export const HOME_ROUTE = route("/", { searchParams: { vue: vueSignal } });
|
|
|
258
276
|
The signal and the URL are the same state: never keep a `useState` beside a route
|
|
259
277
|
param for the same fact. Declared on the **root route**, a search param holds
|
|
260
278
|
wherever one is in the application; declared on one route, it exists only there.
|
|
279
|
+
A route declared "this and below" (`/map/`) lends its params to every address
|
|
280
|
+
below it; `/places` lends nothing, so a `?q=` of its own and the `?q=` of
|
|
281
|
+
`/places/clubs` are two states, even with a `/places/:section` matching both.
|
|
261
282
|
|
|
262
283
|
Writing it AMENDS the history entry one is on rather than stacking a new one: a
|
|
263
284
|
param that qualifies a screen is not a place, and one entry per write turns a
|
|
@@ -398,6 +419,14 @@ section it does NOT stand for. Both amend what a link claims about itself and
|
|
|
398
419
|
nothing else — neither is an answer to a route that lies about where the reader
|
|
399
420
|
is (see [what its address may say](#a-layer-over-the-screen-what-its-address-may-say)).
|
|
400
421
|
|
|
422
|
+
The routing takes a plain click that moves this tab, and nothing else. A link
|
|
423
|
+
with `target="_blank"` (or any `target` other than `_self`) opens the app in that
|
|
424
|
+
tab or window, which loads it and routes there; a link with `download` saves its
|
|
425
|
+
address; a click with a modifier key (a new tab, a new window, a download) is the
|
|
426
|
+
browser's. None of them costs the page the reader is on — a half-filled form
|
|
427
|
+
beside a link to the terms stays filled. Nothing is fetched ahead for such a
|
|
428
|
+
link either: the other tab loads its own code.
|
|
429
|
+
|
|
401
430
|
## The back arrow: `navBack`
|
|
402
431
|
|
|
403
432
|
An arrow drawn inside the app promises the screen the reader came from — never
|
package/docs/pan_zoom.md
CHANGED
|
@@ -91,10 +91,12 @@ is not, and asking again is asking three times for one sentence:
|
|
|
91
91
|
<Box data-pan-after-hold="kept" interactions={{ pan, zoom }} />
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
From the moment the surface
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
From the moment a finger gives the surface the hand — the hold, or a pinch — it
|
|
95
|
+
pans on contact, the way the same plan does opened full screen, until a press
|
|
96
|
+
lands away from it; navi watches for that press itself, so nothing on your side
|
|
97
|
+
listens to the window. A mouse never owed the wait, so it neither spends it nor
|
|
98
|
+
marks it: after a mouse pan the next finger still waits, and the attribute is
|
|
99
|
+
safe to write for every device. The paid wait is a state to draw, and only a
|
|
98
100
|
`"kept"` surface carries it: `[data-hand-kept]`, for as long as it holds the
|
|
99
101
|
hand, a finger on it or not.
|
|
100
102
|
|
package/docs/popup_backdrop.md
CHANGED
|
@@ -138,6 +138,11 @@ wheel above is painted, so a press on it is a press on the popup — which is wh
|
|
|
138
138
|
lets one marker cover a whole row without swallowing the controls it holds. A
|
|
139
139
|
nested box that is see-through too needs its own marker.
|
|
140
140
|
|
|
141
|
+
**Inside a box that travels by drag, the marker answers the click.** A press on
|
|
142
|
+
a row of slides, or on a sheet that swipes closed, may be the start of that
|
|
143
|
+
travel; the popup closes on the click that ends the press, which a travel
|
|
144
|
+
swallows, rather than at the press itself.
|
|
145
|
+
|
|
141
146
|
**Space beside something, not space between two things.** What the wheel does
|
|
142
147
|
not cover is plainly nothing, and a press there reads as a press on the page.
|
|
143
148
|
The gap between two rows of a column does not read that way — it is a seam of
|
package/docs/popup_lift.md
CHANGED
|
@@ -218,9 +218,10 @@ closing, and a name built from it at that render names no tile: build it from
|
|
|
218
218
|
the last key the walk held — a ref written while the signal holds one.
|
|
219
219
|
|
|
220
220
|
**A press on the surface that dismisses is `data-navi-popup-outside`** (see
|
|
221
|
-
[popup_backdrop.md](./popup_backdrop.md#where-the-outside-begins))
|
|
222
|
-
|
|
223
|
-
|
|
221
|
+
[popup_backdrop.md](./popup_backdrop.md#where-the-outside-begins)). The row
|
|
222
|
+
travels, so the marker answers the press's click rather than its mousedown: a
|
|
223
|
+
swipe that starts on the free space walks, and only a press that stayed one
|
|
224
|
+
closes.
|
|
224
225
|
|
|
225
226
|
## Lifting on the way back only
|
|
226
227
|
|
|
@@ -287,9 +287,10 @@ Four misconfigurations are silent enough to be worth a console warning, each sai
|
|
|
287
287
|
once: an area that was not captured (the case above); several elements marked at
|
|
288
288
|
once — they share one `view-transition-name`, and the browser refuses every route
|
|
289
289
|
transition while they do; pages travelling on the document while something else
|
|
290
|
-
is captured on its own — the blank band; and
|
|
291
|
-
|
|
292
|
-
|
|
290
|
+
is captured on its own — the blank band; and a `view-transition-name` written
|
|
291
|
+
inside the area that only one of the two pages has: with nothing to morph into,
|
|
292
|
+
its element stands still and fades while the pages move, in every browser, and
|
|
293
|
+
without nested groups it is not even clipped (see
|
|
293
294
|
[view_transitions.md](./view_transitions.md#a-name-is-unique-per-document)). A
|
|
294
295
|
warning here is a bug to fix, not a mechanism to lean on.
|
|
295
296
|
|
|
@@ -474,6 +475,9 @@ What that means for a page and for the application around it:
|
|
|
474
475
|
it was (`<List id>`) under the same `id` as a list of the page arriving is the
|
|
475
476
|
same list to navi, and the two overwrite each other's position (see
|
|
476
477
|
[scroll.md](./scroll.md#a-list-that-comes-back-one-id-one-list)).
|
|
478
|
+
They let go of the document once hidden: a list scrolling the page stops
|
|
479
|
+
placing it on the item it opened on, and stops answering its scrolls and
|
|
480
|
+
resizes, so the page arriving lands where it was left, or at its top.
|
|
477
481
|
- **Never while a modal dialog is open.** An open modal keeps everything but
|
|
478
482
|
itself out of reach — the page arriving could not even take the focus — and one
|
|
479
483
|
in a page kept while leaving would stay open for the whole movement. The page is
|
package/docs/scroll.md
CHANGED
|
@@ -117,8 +117,8 @@ say so with `"document"` or the element itself.
|
|
|
117
117
|
### Where the list opens, and where it is
|
|
118
118
|
|
|
119
119
|
`defaultScrolled` (or `scrolled`, held) takes `{id, offset}` — what
|
|
120
|
-
`onScrolledChange` hands out — and asks for the
|
|
121
|
-
by MEASURING it: it lands where it was even if
|
|
120
|
+
`onScrolledChange` hands out — and asks for the item BY NAME, then puts it back
|
|
121
|
+
by MEASURING it: it lands where it was even if items were inserted before it,
|
|
122
122
|
whatever the screen it was saved on. "Reopen a thread where I left it" is
|
|
123
123
|
already provided; keep the position whole, its `visibleCount` sizes the first
|
|
124
124
|
paint. A `defaultScrolled` that changes before anyone has moved the list is
|
|
@@ -129,9 +129,9 @@ must name one list of the app, see [below](#a-list-that-comes-back-one-id-one-li
|
|
|
129
129
|
**`scrollResetOnNavigation`** opts out. What it draws there before its source
|
|
130
130
|
answers is another matter
|
|
131
131
|
([What a run knows when it opens](#what-a-run-knows-when-it-opens)). A list scrolling the document that
|
|
132
|
-
opens on
|
|
133
|
-
is not put back over it — the
|
|
134
|
-
height, and the same pixels now fall on other
|
|
132
|
+
opens on an item places the document itself: the offset the page kept for its url
|
|
133
|
+
is not put back over it — the items held off screen are fillers of an estimated
|
|
134
|
+
height, and the same pixels now fall on other items.
|
|
135
135
|
|
|
136
136
|
### A list that comes back: one `id`, one list
|
|
137
137
|
|
package/docs/testid.md
CHANGED
|
@@ -76,11 +76,14 @@ Navi hands the host the props it knows belong there (`id`, `name`, `type`,
|
|
|
76
76
|
root box instead — so a `data-test-id` or a `data-qa` of your own would name
|
|
77
77
|
the wrapper, which is the second reason to keep the standard spelling.
|
|
78
78
|
|
|
79
|
-
`aria-label` and `aria-labelledby`
|
|
80
|
-
|
|
81
|
-
role — it is a wrapper — so a name left on it would be
|
|
82
|
-
found by nothing, while the host is what the user
|
|
83
|
-
`getByRole(role, { name })` and `getByLabel()` read.
|
|
79
|
+
`aria-label` and `aria-labelledby` follow the role, for the reason the section
|
|
80
|
+
above gives: the name has to be where the role is. For most controls that is the
|
|
81
|
+
host. The root box has no role — it is a wrapper — so a name left on it would be
|
|
82
|
+
announced by nothing and found by nothing, while the host is what the user
|
|
83
|
+
focuses and what `getByRole(role, { name })` and `getByLabel()` read. A `Wheel`
|
|
84
|
+
is the other way round: its root is the `spinbutton` the user focuses, and its
|
|
85
|
+
host is an `aria-hidden` input carrying the value, so the name lands on the
|
|
86
|
+
root.
|
|
84
87
|
|
|
85
88
|
```jsx
|
|
86
89
|
<Picker variant="icon" aria-label="Aide" ui={<Icon>…</Icon>}>
|
package/docs/view_transitions.md
CHANGED
|
@@ -33,8 +33,9 @@ and `List` unnames its rows for the length of any route transition or travel. A
|
|
|
33
33
|
name the app puts inside the pages is given only for the gesture it serves, or
|
|
34
34
|
dropped while they move: `view-transition-name: none` under
|
|
35
35
|
`:root:is([data-navi-route-transition], [data-navi-route-travel])`. Without nested
|
|
36
|
-
groups it also escapes the pages' clip
|
|
37
|
-
transition area
|
|
36
|
+
groups it also escapes the pages' clip. navi warns about a name inside a
|
|
37
|
+
transition area that only one of the two pages has: with nothing to morph into,
|
|
38
|
+
it can only be this trap.
|
|
38
39
|
|
|
39
40
|
## Nested groups, and the fallback fade
|
|
40
41
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/navi",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.479",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Library of components including navigation to create frontend applications",
|
|
6
6
|
"repository": {
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@jsenv/dom": "0.17.94",
|
|
38
|
-
"@jsenv/humanize": "1.8.
|
|
39
|
-
"@jsenv/validity": "0.4.
|
|
38
|
+
"@jsenv/humanize": "1.8.8",
|
|
39
|
+
"@jsenv/validity": "0.4.6"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@jsenv/assert": "4.5.7",
|